Розширення функціональності інструменту автоматизованого тестування USD

pic

Исходный код

Вступ

Після завершення частини 2 мого проекту, я тепер планую розширити функціональність самого інструмента. Частина 1 цього проекту створила дуже міцний, модульний фундамент для подальшого розвитку цього інструменту, який я збираюся розширювати в цій частині (частина 3).

Процес проекту

Покращення моєї конфігурації CMake

Після розмови з другом, який є дуже досвідченим розробником програмного забезпечення, я зрозумів, що є кілька способів покращити конфігурацію CMake мого проекту. Ось розбір того, чого я навчився і як я покращую свій файл CMakeLists.txt.

1. Встановлення USD_ROOT як змінної кешу

Я оновлюю USD_ROOT, щоб він став змінною кешу.
Це дозволяє будь-кому, хто використовує мій проект, легко передавати своє власне значення під час налаштування. Я також додаю тип і чіткий опис, щоб це було більш інтуїтивно зрозуміло для інших користувачів. Ця гнучкість особливо важлива, оскільки користувачам потрібно буде вказати шлях до їхньої власної установки USD, незалежно від того, чи використовують вони попередньо зібрані бінарники, чи кастомну збірку.

2. Використання попередньо зібраних файлів USD або сценаріїв збірки

Згідно з моєю розмовою, здавалося, що я можу використати файл FindUSD.cmake або USDConfig.cmake, щоб спростити знаходження USD. Однак, виходячи з репозиторію Pixar OpenUSD, зрозуміло, що користувачі повинні або завантажити попередньо зібрані бінарники, або запустити наданий сценарій збірки для генерації необхідних файлів USD. Оскільки ці бінарники/сценарії не включають USDConfig.cmake, я буду покладатися на ручне налаштування USD_ROOT, замість того, щоб намагатися створити чи використовувати файл FindUSD.cmake. Цей підхід гарантує, що моя конфігурація залишатиметься узгодженою з процесом налаштування OpenUSD.

3.
Уникання
include_directories та link_libraries

Я переходжу від старих команд include_directories і link_libraries, які застосовують налаштування глобально для всіх цілей. Натомість я використовую target_include_directories та target_link_libraries, щоб зробити конфігурацію більш модульною та специфічною для окремих цілей. Мій проект наразі має лише одну ціль, але цей підхід забезпечить майбутню гнучкість і зробить налаштування чистішим.

# До:  
include_directories("${USD_ROOT}/include")  

# Після:  
target_include_directories(usdTestRunner PRIVATE "${USD_ROOT}/include")

4. Використання Python:: цілей замість ${Python3_*} змінних

Працюючи з Python у CMake, я дізнався, що краще використовувати цілі Python:: замість старих змінних ${Python3_*}. Новіші цілі автоматично обробляють включення директорій та залежності, що робить конфігурацію більш надійною та менш схильною до помилок.

5.
Спрощення виклику
find_package для Python

Оскільки USD вимагає Python, це є необхідною частиною моєї конфігурації. Я використовую команду find_package з компонентом лише для Development (не потрібно використовувати Interpreter, оскільки я не вбудовую Python). Ось оновлений виклик:

find_package(Python COMPONENTS Development)

Далі я зв’язуюсь з Python::Python, що автоматично обробляє включення директорій та залежності. Це забезпечує правильну конфігурацію мого проекту для підтримки USD і його Python-залежностей.

# Старий підхід:  
include_directories(${Python3_INCLUDE_DIRS})  
target_link_libraries(usdTestRunner ${Python3_LIBRARIES})  

# Новий підхід:  
target_link_libraries(usdTestRunner Python::Python) # Все обробляється автоматично

Нижче наведений оновлений файл CMakeLists.txt:

# Цей CMakeLists.txt конфігурує проект для автоматизованого тестування USD (Universal Scene Description).  
# Він налаштовує C++ проект, що працює з бібліотеками USD та Python.

# Передумови:  
# - CMake версії 3.16 або новішої  
# - C++ компілятор з підтримкою C++17  
# - Встановлені бібліотеки USD  
# - Python 3 з заголовками для розробки  

# Визначення мінімальної необхідної версії CMake  
cmake_minimum_required(VERSION 3.16)  

# Визначення назви проекту та мови  
project(AutomatedUSDTesting CXX)  

# Налаштування C++17 як необхідного стандарту для проекту  
set(CMAKE_CXX_STANDARD 17)  
set(CMAKE_CXX_STANDARD_REQUIRED ON)  

# Запобігання визначенню макросів min/max в Windows.h, які можуть конфліктувати з std::min/std::max  
add_definitions(-DNOMINMAX)  

# Встановлення директорії установки USD як змінної кешу з типом і описом  
set(USD_ROOT "" CACHE PATH "Шлях до директорії установки USD")  

# Встановлення стандартного шляху для установки USD, якщо він не був вказаний  
if(USD_ROOT STREQUAL "")  
 if(WIN32)  
 set(USD_ROOT "C:/Users/lwolu/OneDrive/Documents/Coding/dev/usd-automated-testing/usd" CACHE PATH "Стандартний шлях USD для Windows" FORCE)  
 else()  
 set(USD_ROOT "/usr/local/USD" CACHE PATH "Стандартний шлях USD для Linux" FORCE)  
 endif()  
endif()  

# Виведення шляху установки USD, що використовується  
message("Використовується USD_ROOT=${USD_ROOT}")  

# Автоматичне виявлення бібліотек USD залежно від платформи  
if(WIN32)  
 # Windows використовує .lib файли як імпортні бібліотеки  
 file(GLOB USD_LIBS "${USD_ROOT}/lib/*.lib")  
else()  
 # Linux системи використовують .so спільні бібліотеки  
 file(GLOB USD_LIBS "${USD_ROOT}/lib/libusd*.so")  
endif()  

# Налаштування залежностей для Python  
# Використовуємо тільки компонент Development, оскільки інтерпретатор не потрібен  
find_package(Python COMPONENTS Development)  

# Налаштування специфічних для компілятора заборон на попередження  
if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU")  
 # Специфічні для GCC заборони на попередження  
 add_compile_options(-Wno-deprecated -Wno-pragmas -DTBB_SUPPRESS_DEPRECATED_MESSAGES=1)  
elseif (CMAKE_CXX_COMPILER_ID STREQUAL "Clang")  
 # Специфічні для Clang заборони на попередження  
 add_compile_options(-Wno-deprecated-declarations -DTBB_SUPPRESS_DEPRECATED_MESSAGES=1)  
endif()  

# Визначення головного виконуваного файлу  
add_executable(usdTestRunner src/main.cpp)  

# Налаштування специфічних для цілі включених директорій та залежностей бібліотек  
target_include_directories(usdTestRunner PRIVATE "${USD_ROOT}/include")  
target_link_libraries(usdTestRunner PRIVATE ${USD_LIBS} Python::Python)  

# Налаштування шляху пошуку бібліотек під час виконання (RPATH) для Linux систем  
# Це забезпечує правильне знаходження бібліотек USD під час виконання  
if(NOT WIN32)  
 set_target_properties(usdTestRunner PROPERTIES  
 BUILD_RPATH "${USD_ROOT}/lib"  
 INSTALL_RPATH "${USD_ROOT}/lib"  
 )  
endif()

Основні переваги цих змін:

  • Зручніша конфігурація для користувачів
  • Краща модульність завдяки налаштуванням, специфічним для цілей
  • Сучасні найкращі практики CMake
  • Спрощена інтеграція з Python

Обсяг проекту згідно з моїми попередніми ідеями

Згідно з моїми ідеями для подальшого розвитку після першої частини проекту, я маю на меті покращити існуючий інструмент, додаючи можливість виконання тестів на вимогу користувачів, покращене обмінювання результатами та розширену функціональність оцінки.
Це забезпечить більшу надійність при валідації конфігурацій USD.

Я планував реалізувати наступне для цієї частини проекту (частина 3):

  • Цілеспрямовані операції тестування: Дозволити користувачам вказувати конкретні операції тестування через командні прапорці (наприклад, -only-geometry або -skip-shaders) для більшої гнучкості.
  • Експортувані результати: Дозволити збереження результатів валідації у текстовому або JSON форматах, що зробить їх зручнішими для обміну та перегляду.
  • Розширена оцінка: Ввести підтримку додаткових можливостей оцінки для існуючих тестів, а також створення нових тестів.
  • Розширене покриття тестами: Додати більше тестових випадків для обробки крайових сценаріїв, забезпечуючи надійність проти незвичних або недійсних конфігурацій USD.

Цілеспрямовані операції тестування

Оскільки користувачі інструменту не завжди потребують або не хочуть запускати всі тести на конкретному файлі, я вирішив покращити досвід користувачів, дозволивши їм вибирати конкретні операції тестування через командні прапорці (наприклад, -only-geometry або -skip-shaders) для більшої гнучкості.

Я реалізував командні прапорці, які дозволяють користувачам:
- Запускати лише конкретні тести, використовуючи прапорці -only-*
- Пропускати конкретні тести за допомогою прапорців -skip-*
- Переглядати інформацію про використання за допомогою прапорця -help

Ось як виглядає реалізація:

**1.
Структура конфігурації тестів

Спочатку я створив структуру TestConfig для відстеження того, які тести повинні виконуватись:

struct TestConfig {  
 bool runGeometry = true;  
 bool runShaders = true;  
 bool runLayers = true;  

 // Повертає true, якщо хоча б один тест увімкнено  
 bool hasEnabledTests() const {  
 return runGeometry || runShaders || runLayers;  
 }  
};

Ця структура забезпечує чистий спосіб управління прапорцями виконання тестів і гарантує, що хоча б один тест залишатиметься увімкненим.

**2.
Покращений запуск тестів

Я модифікував клас TestRunner, щоб він приймав ідентифікатори тестів та враховував конфігурацію:

class TestRunner {  
public:  
 void addTest(const std::string& id, const ValidationFunction& test) {  
 tests.push_back({id, test});  
 }  

 void runTests(const TestConfig& config) {  
 // … ініціалізація етапу …  

 for (const auto& [id, test] : tests) {  
 if ((id == "geometry" && config.runGeometry) ||  
 (id == "shaders" && config.runShaders) ||  
 (id == "layers" && config.runLayers)) {  
 TestResult result = test(stage);  
 report(result);  
 }  
 }  
 summarize();  
 }  

private:  
 std::vector> tests;  
 // … інші члени …  
};

Ключові зміни:
- Тепер тести зберігаються з ідентифікаторами
- runTests використовує конфігурацію для визначення, які тести виконувати
- Виконання тестів умовне на основі прапорців конфігурації

**3.
Парсинг аргументів командного рядка

Я реалізував надійний парсинг аргументів для обробки нових прапорців:

TestConfig parseArguments(int argc, char* argv[]) {  
 TestConfig config;  
 std::unordered_set args;  

 // Збір аргументів  
 for (int i = 1; i < argc; ++i) {  
 args.insert(argv[i]);  
 }  

 // Обробка прапорців 'only'  
 if (args.count("-only-geometry")) {  
 config.runGeometry = true;  
 config.runShaders = false;  
 config.runLayers = false;  
 } else if (args.count("-only-shaders")) {  
 config.runGeometry = false;  
 config.runShaders = true;  
 config.runLayers = false;  
 } else if (args.count("-only-layers")) {  
 config.runGeometry = false;  
 config.runShaders = false;  
 config.runLayers = true;  
 } else {  
 // Обробка прапорців 'skip'  
 if (args.count("-skip-geometry")) config.runGeometry = false;  
 if (args.count("-skip-shaders")) config.runShaders = false;  
 if (args.count("-skip-layers")) config.runLayers = false;  
 }  

 // Перевірка конфігурації  
 // … логіка перевірки …  

 return config;  
}

Цей парсер:
- Обробляє як прапорці -only-*, так і -skip-*
- Забезпечує, щоб прапорці не використовувались некоректно
- Підтримує хоча б один увімкнений тест
Виведення допомоги

Я додав докладну інформацію про допомогу:

void displayHelp() {  
 std::cout << R"(  
Usage: usdTestRunner  [options]  

Options:  
 -only-geometry Виконати тільки валідацію геометрії  
 -only-shaders Виконати тільки валідацію шейдерів  
 -only-layers Виконати тільки валідацію структури шарів  
 -skip-geometry Пропустити валідацію геометрії  
 -skip-shaders Пропустити валідацію шейдерів  
 -skip-layers Пропустити валідацію структури шарів  
 -help Вивести це повідомлення допомоги  

Note:  
 - Прапорці 'only' і 'skip' взаємно виключають один одного  
 - Можна комбінувати кілька прапорців 'skip'  
 - Можна використовувати тільки один прапорець 'only' одночасно  
)";  
}

Оновлена головна функція

Нарешті, я змінюю головну функцію для використання нових компонентів:

int main(int argc, char* argv[]) {
// Спочатку перевіряємо прапорець допомоги
if (argc > 1 && std::string(argv[1]) == "-help") {
displayHelp();
return 0;
}

// Якщо відсутні аргументи
if (argc < 2) {
displayHelp();
return 1;
}

const std::string usdFilePath = argv[1];
TestRunner runner(usdFilePath);

// Додаємо тести з їх ідентифікаторами
runner.addTest("geometry", validateGeometry);
runner.addTest("shaders", validateShaders);
runner.addTest("layers", validateLayerStructure);

// Парсимо аргументи та виконуємо тести
TestConfig config = parseArguments(argc, argv);
runner.runTests(config);

return 0;
}
```

Приклади використання

Тепер користувачі можуть запускати тести різними способами:

# Виконати всі тести (поведінка за замовчуванням)  
./usdTestRunner path/to/file.usda  
# Виконати тільки валідацію геометрії  
./usdTestRunner path/to/file.usda -only-geometry  
# Пропустити валідацію шейдерів  
./usdTestRunner path/to/file.usda -skip-shaders  
# Пропустити кілька тестів  
./usdTestRunner path/to/file.usda -skip-geometry -skip-shaders  
# Вивести допомогу  
./usdTestRunner -help

Ці зміни значно покращують гнучкість інструменту, дозволяючи користувачам зосередитися на конкретних аспектах валідації файлів USD.
Реалізація підтримує чисту організацію коду, одночасно додаючи потужний функціонал через простий інтерфейс командного рядка.

Модульний дизайн також робить легким додавання нових типів тестів у майбутньому, що дозволяє інструменту розвиватися разом з потребами користувачів.

Експортувані результати

Оскільки я створив конвеєр CI/CD Jenkins в частині 2 цього проєкту, який генерує реальні результати для порівняння з очікуваними, я зрозумів, що буде корисно надати користувачам можливість зберігати ці результати для подальшого перегляду, обміну з членами команди або інтеграції в автоматизовані конвеєри. Дозволивши зберігати результати валідації у текстовому форматі, їх стає простіше ділитися та переглядати.

Спочатку валідатор USD лише виводив результати тестів у консоль.
Це означало:

  • Результати не можна було легко зберегти для подальшого перегляду
  • Інтеграція з CI/CD конвеєрами вимагала парсингу виводу консолі
  • Обмін результатами з членами команди вимагав ручного копіювання
  • Пайплайни Jenkins потребували додаткових зусиль для захоплення та обробки результатів

Я реалізував функцію експорту в файл, яка:

  • Зберігає результати валідації у текстовому форматі
  • Зберігає той самий формат, що і вивід у консоль
  • Інтегрується з існуючими пайплайнами Jenkins
  • Може бути поєднана з флагами вибору тестів

Ось як виглядає реалізація:

1. Оновлення конфігурації

По-перше, я додав підтримку файлу виводу до структури TestConfig:

struct TestConfig {  
 bool runGeometry = true;  
 bool runShaders = true;  
 bool runLayers = true;  
 std::string outputPath; // Нове поле для шляху до файлу виводу  
bool hasEnabledTests() const {  
 return runGeometry || runShaders || runLayers;  
 }  
};

Це просте доповнення дозволяє відстежувати, чи потрібно зберігати результати, і де саме їх зберігати.
Збір виводу**

Я змінив клас TestRunner, щоб збирати вивід під час його генерації:

class TestRunner {  
private:  
 std::stringstream output; // Новий член для збору виводу  

 void report(const TestResult& result) {  
 results.push_back(result);  
 std::string resultStr = "[" + std::string(result.passed ? "PASS" : "FAIL") + "] "   
 + result.testName + ": " + result.message + "\n";  

 std::cout << resultStr;  
 output << resultStr; // Зберігаємо у stringstream для подальшого експорту  
 }  
};

Основні зміни:
- Додано stringstream для збереження всього виводу
- Змінено механізм звітування для збереження виводу під час його генерації
- Збережено ідентичне форматування між виводом у консолі та файлом
Реалізація експорту файлів**

Я додав новий метод для обробки експорту в файл:

void exportResults(const std::string& filePath) {  
 std::ofstream outFile(filePath);  

 if (!outFile) {  
 std::cerr << "Помилка: Не вдалося відкрити вихідний файл: " << filePath << "\n";  
 return;  
 }  

 outFile << output.str();  
 std::cout << "Результати експортовано до: " << filePath << "\n";  
}

Цей метод:
- Відкриває вказаний вихідний файл
- Записує весь зібраний вивід
- Коректно обробляє випадки помилок
- Підтверджує успішний експорт для користувача
Інтеграція з командним рядком**

Я оновив розбір аргументів, щоб обробляти новий прапор -output:

TestConfig parseArguments(int argc, char* argv[]) {  
 TestConfig config;  

 for (int i = 1; i < argc; ++i) {  
 // Перевірка на шлях до файлу  
 if (std::string(argv[i]) == "-output" && i + 1 < argc) {  
 config.outputPath = argv[i + 1];  
 ++i; // Пропускаємо наступний аргумент, оскільки це шлях  
 }  

 // … решта розбору аргументів …  
 }  

 return config;  
}

А також оновив відображення допомоги:

void displayHelp() {  
 std::cout << R"(  
Usage: usdTestRunner  [options]  

Options:  
 … існуючі параметри …  
 -output  Експортувати результати до вказаного шляху файлу  
 … решта тексту допомоги …  
)";
}

Оновлення виконання тестів**

Нарешті, я змінив потік виконання тестів для обробки експорту файлів:

void runTests(const TestConfig& config) {
// Очищення попереднього стану
results.clear();
output.str("");

// … виконання тестів …

// Експортувати результати, якщо вказано шлях до файлу
if (!config.outputPath.empty()) {
exportResults(config.outputPath);
}
}
```

Приклади використання

Нову функціональність можна використовувати різними способами:

# Основний експорт результатів  
./usdTestRunner scene.usda -output results.txt  
# Поєднання з вибором тестів  
./usdTestRunner scene.usda -only-geometry -output geo_results.txt  
# Пропустити тести та зберегти результати  
./usdTestRunner scene.usda -skip-shaders -output validation.txt

Приклад вмісту файлу результатів:

$ ./usdTestRunner /test/empty/empty.usda -skip-shaders  

Відкрито USD файл успішно.  

[FAIL] Validate Geometry: Не знайдено дійсних геометричних примітивів у сцені.  
[PASS] Validate Layer Structure: Стек шарів є дійсним.  

Резюме:  
 Пройшло: 1  
 Не пройшло: 1  

Деякі тести не пройшли. Будь ласка, перегляньте USD файл та виправте проблемні тести.

Чому саме такий підхід?

  1. Текстовий формат: Я вибрав виведення в простому текстовому форматі, оскільки:
    — Це відповідає моїм очікуванням для Jenkins pipeline
    — Легко читається без спеціальних інструментів
    — Зберігається консистентність з виведенням в консолі
    — Легко парситься та обробляється, якщо це потрібно

  2. Потокове виведення: Використання stringstream для збору виведення під час його генерування означає:
    — Я зберігаю виведення лише один раз
    — Використання пам'яті залишається сталим
    — Я зберігаю точну консистентність формату

  3. Інтеграція: Ця функціональність безперешкодно інтегрується з існуючою:
    — Працює з усіма флагами вибору тестів
    — Зберігається той самий формат виведення
    — Потрібно мінімум змін в існуючому коді

Розширена оцінка

Коли я вперше створював набір інструментів для валідації USD, я зосередився на трьох основних напрямках: Геометрія, Шейдери та Структура шарів.

Ці початкові функції стали чудовою відправною точкою для перевірки наявності геометрії та правильності її налаштувань, коректності шейдерів та складання стеків шарів. Однак, коли мої активи USD стали складнішими, я зрозумів, що потрібно розширити перевірки в цих областях. Ось огляд того, як я покращив ці функції, а також введення нової функції validateVariants.

Функція validateVariants додає додатковий рівень ретельності до конвеєра. Якщо ви не знайомі з терміном "варіанти" в контексті OpenUSD, ось чудове пояснення того, що це таке, відповідно до документації Omniverse USD від Nvidia.

"Набір варіантів у OpenUSD представляє собою колекцію альтернативних уявлень або конфігурацій для прима."

Наприклад, набір варіантів може включати різні варіації матеріалів, різні рівні деталізації (LOD), або різні геометричні представлення моделі.”

pic

Джерело: Представлення варіантів для заданого активу

1.
Перевірка Геометрії

Функція validateGeometry є основною для перевірки, чи містить сцена дійсні геометричні примітиви (наприклад, UsdGeomXform, UsdGeomMesh) і чи відповідають вони кільком важливим критеріям.

Поліпшення:

  • Наявність дійсних геометричних примітивів: Сканація примітивів на основі Xform і підтвердження їхніх операцій трансформацій.
  • Дійсні операції трансформацій: Перегляд кожної операції UsdGeomXformOp для позначення недійсних атрибутів USD.
  • Перевірки обсягів і точок на сітках: Перевірка обмежувальних коробок і даних точок, а також виявлення дегенеративної геометрії (наприклад, коли min == max в масиві обсягів).

Основні зміни в коді:

bool resetXformStack;  
std::vector xformOps = xformable.GetOrderedXformOps(&resetXformStack);  
for (const auto& op : xformOps) {  
 if (!op.GetAttr()) {  
 errors.push_back("Невірна операція трансформації на шляху: " + prim.GetPath().GetString());  
 }  
}  

if (auto mesh = pxr::UsdGeomMesh(prim)) {  
 auto extentAttr = mesh.GetExtentAttr();  
 if (extentAttr) {  
 pxr::VtVec3fArray extent;  
 if (extentAttr.Get(&extent)) {  
 if (extent.size() == 2) {  
 const auto& min = extent[0];  
 const auto& max = extent[1];  
 if (min == max) {  
 errors.push_back("Дегенеративна геометрія на шляху: " + prim.GetPath().GetString());  
 }  
 }  
 }  
 }  
}

Ці доповнення дозволяють рано виявляти недійсні трансформації та дегенеративну геометрію, що спрощує виявлення проблем до того, як вони стануть серйозними.
Перевірки обмежувальних коробок і даних точок гарантують, що вся геометрія є повною та дійсною для використання.

2.
Перевірка шейдерів (Shader Validation)

Далі я зосередився на вдосконаленні функції validateShaders, щоб переконатися, що мережі шейдерів правильно налаштовані та повністю вирішені.

Покращення:

  • Існування шейдера та джерела активів: Перевірка наявності хоча б одного дійсного шейдера та перевірка шляху до його джерела активів (SdfAssetPath).
  • Параметри шейдера та з'єднання: Перевірка, що всі вхідні дані шейдера присутні та правильно з'єднані.
  • ID шейдера та зв'язки з матеріалами: Перевірка, що шейдери мають дійсні ID, а з'єднання матеріалів із поверхнями ведуть до дійсних примів.

Ключові додавання в код:

for (const auto& input : inputs) {  
 pxr::UsdShadeConnectableAPI source;  
 pxr::TfToken sourceName;  
 pxr::UsdShadeAttributeType sourceType;  

 if (input.GetConnectedSource(&source, &sourceName, &sourceType)) {  
 if (!source.GetPrim().IsValid()) {  
 errors.push_back("Invalid shader connection at: " + input.GetBaseName().GetString());  
 }  
 }  
}  

pxr::TfToken shaderId;  
if (!shader.GetShaderId(&shaderId) || shaderId.IsEmpty()) {  
 errors.push_back("Missing or invalid shader ID at: " + prim.GetPath().GetString());  
}

Перевіряючи з'єднання шейдерів та їх ID, функція тепер позначає відсутні джерела активів, неправильні з'єднання та інші типові проблеми в мережах шейдерів.
Ці оновлення спрощують процес розробки вигляду (look-development), забезпечуючи, щоб шейдери були повними та коректно посиланими.

3.
Валідація структури шарів (Layer Structure Validation)

Я також покращив функцію validateLayerStructure, щоб забезпечити відсутність помилок композиції та відсутніх посилань у стеку шарів USD.

Покращення:

  • Повнота стеку шарів: Перевірка на порожні або зламані стекі шарів.
  • Дубльовані ідентифікатори та підшари: Виявлення дубльованих ідентифікаторів і перевірка шляхів підшарів.
  • Композиційні дуги: Перевірка, щоб посилання та навантаження у кореневих прим-специфікаціях кожного шару були валідними.

Ключові додавання коду:

for (const auto& subLayerPath : layer->GetSubLayerPaths()) {  
 auto subLayer = pxr::SdfLayer::FindOrOpen(subLayerPath);  
 if (!subLayer) {  
 errors.push_back("Невирішений підшар: " + std::string(subLayerPath));  
 continue;  
 }  

for (const auto& ref : subLayer->GetExternalReferences()) {  
 if (!pxr::SdfLayer::FindOrOpen(ref)) {  
 errors.push_back("Зламане зовнішнє посилання в підшарі: " + ref);  
 }  
 }  
}

Ці покращення забезпечують правильне вирішення шляхів підшарів та зовнішніх посилань, запобігаючи відсутнім активам або помилкам композиції, які можуть перешкодити більшим сценам.

4.
Валідація варіантів — нова всебічна перевірка

Для вирішення більш складних налаштувань я додав функцію validateVariants.
Ця функція забезпечує безпечне опрацювання USD-ресурсів з кількома варіантами (наприклад, варіанти геометрії, варіанти освітлення або художні варіації).

Основні можливості:

  • Ідентифікація наборів варіантів: Перевірка наявності наборів варіантів та їх використання.
  • Перевірка назв варіантів і вибору: Переконання, що немає порожніх назв варіантів або некоректних виборів.
  • Уникання кругових залежностей: Виявлення вкладених наборів варіантів, що можуть викликати нескінчену рекурсію.
  • Обробка виключень: Ловлення і звітування про проблеми без краху пайплайна.

Основні додавання до коду:

for (const auto& variantName : variantNames) {  
 try {  
 if (!varSet.SetVariantSelection(variantName)) {  
 errors.push_back("Не вдалося встановити варіант '" + variantName + "' у наборі '" + setName + "' на: " + prim.GetPath().GetString());  
 continue;  
 }  

auto variantPrim = stage->GetPrimAtPath(prim.GetPath());  
 if (!variantPrim.IsValid()) {  
 errors.push_back("Некоректний прим у варіанті '" + variantName + "' на: " + prim.GetPath().GetString() + " (набір: " + setName + ")");  
 }  
 if (variantPrim) {  
 auto nestedVarSets = variantPrim.GetVariantSets();  
 std::vector nestedNames;  
 nestedVarSets.GetNames(&nestedNames);  
 for (const auto& nestedName : nestedNames) {  
 if (nestedName == setName) {  
 errors.push_back("Виявлено кругову залежність варіантів на: " + prim.GetPath().GetString() + " (набір: " + setName + ", варіант: " + variantName + ")");  
 }  
 }  
 }  
 } catch (const std::exception& e) {  
 errors.push_back("Виключення під час обробки варіанту '" + variantName + "' у наборі '" + setName + "' на: " + prim.GetPath().GetString() + " - " + e.what());  
 }  
}

Функція validateVariants охоплює важливу область для складних налаштувань USD.
Це забезпечує те, що варіанти використовуються належним чином, уникнення кругових залежностей і надає чіткий зворотний зв'язок щодо проблем, що робить її безцінним доповненням до набору перевірок.

Вплив змін

З оновленими функціями перевірки для геометрії, шейдерів і шарів, а також новим додаванням перевірки варіантів, мій набір тестів для USD еволюціонував від базового виявлення помилок до всеосяжного інструмента перевірки. Кожна функція тепер вирішує конкретний аспект специфікації USD, виявляючи тонкі помилки у структурі або вмісті файлів і гарантуючи, що всі посилання, з'єднання та композиційні дуги визначені правильно та функціонують. Ці покращення особливо цінні, оскільки ресурси USD стають більш складними та багатошаровими.
Ці перевірки допомагають підтримувати послідовність і надійність у ваших робочих процесах USD.

Розширене покриття тестами

Після покращення моїх початкових функцій перевірки та створення нової для перевірки варіантів, я додав кілька тестових файлів для покриття крайніх випадків та реальних сценаріїв. Серед них:

  • bad_file: Файл з навмисними помилками для тестування випадків помилок.
  • ball_shading_variants: Детальніший приклад файлу з репозиторію OpenUSD для тестування функціональності варіантів.
  • kitchen_set: Складний сценічний файл з OpenUSD, який представляє великі виробничі середовища.

pic

Джерело: Kitchen PUP Asset

Під час запуску мого інструмента з цими новими тестовими випадками, я виявив проблеми, які змусили мене переписати всі існуючі функції та мою нову функцію перевірки варіантів. Під час цього процесу я усвідомив фундаментальну помилку в моїй початковій логіці.
Я спроектував валідатори так, щоб вони не проходили тести, якщо в файлі USD відсутні певні елементи, такі як геометрія, шейдери або варіанти. Однак цей підхід не враховував того, що багато файлів USD є валідними без цих елементів, особливо простіші файли.

Я переналаштував свої функції на пошук явно неправильних або пошкоджених даних, таких як дегенеративна геометрія, нульові посилання або неправильно сформовані варіанти, замість того, щоб не проходити тести через відсутність елементів. Це оновлення забезпечує:

  • Тести тепер не провалюються, якщо щось не є неправильним.
  • Файли без певних елементів (наприклад, шейдерів або варіантів) успішно проходять відповідні тести, що відображає їхню валідність у реальних робочих процесах.

Такий підхід робить мій інструмент більш надійним і адаптивним до широкого спектра конфігурацій файлів USD, при цьому все ще виявляючи критичні помилки.
Обробляючи відсутні елементи як прийнятні, коли це доречно, інструмент тепер більш дружелюбний до різноманітних випадків використання та краще відповідає реальним практикам USD.

Нижче наведені деякі основні зміни, які я реалізував для кожної з моїх тестових функцій:

1) Перевірка геометрії

Фрагмент коду (виявлення дегенеративної геометрії):

if (extentArray.size() == 2) {  
 const auto& min = extentArray[0];  
 const auto& max = extentArray[1];  
 if (min == max) {  
 errors.push_back("Degenerate geometry found at: " + prim.GetPath().GetString());  
 }  
}

Що: Я перевіряю атрибут extent у UsdGeomMesh, щоб побачити, чи однакові мінімальні та максимальні точки.

Чому: Якщо min == max, це означає, що об'єм дорівнює нулю, що фактично означає, що сітка є дегенеративною.

Як: Порівнюючи кути обмежувального ящика, я швидко визначаю, чи є геометрія валідною або "площинною", і в разі дегенеративної геометрії викликаю помилку.

2) Перевірка шейдерів

Фрагмент коду (перевірка валідності ID шейдера):

pxr::TfToken shaderId;  
shader.GetShaderId(&shaderId);  
if (shaderId.IsEmpty()) {  
 errors.push_back("Missing or invalid shader ID at: " + prim.GetPath().GetString());  
}

Що: Я запитую токен info:id для кожного UsdShadeShader, щоб підтвердити, що він має реальне значення.

Чому: Деякі пайплайни вимагають, щоб ID був розпізнаваним (наприклад, UsdPreviewSurface) або хоча б не порожнім рядком.
Якщо ID порожній, я вважаю шейдер неповним або недійсним.

Як: Використовуючи GetShaderId(...), я зберігаю токен у змінній shaderId. Якщо він порожній, я повідомляю про помилку в errors.

3) Перевірка структури шарів

Фрагмент коду (розв'язання підшарів та перевірка посилань):

for (const auto& subLayerPath : layer->GetSubLayerPaths()) {  
 auto subLayer = pxr::SdfLayer::FindOrOpen(subLayerPath);  
 if (!subLayer) {  
 errors.push_back("Unresolved sublayer: " + std::string(subLayerPath));  
 continue;  
 }  
 // Перевірка зовнішніх посилань:  
 for (const auto& ref : subLayer->GetExternalReferences()) {  
 if (!pxr::SdfLayer::FindOrOpen(ref)) {  
 errors.push_back("Broken external reference in sublayer: " + ref);  
 }  
 }  
}

Що: Для кожного шару я перевіряю його підшари та намагаюся їх відкрити.
Я також перевіряю зовнішні посилання, оголошені в цих підшарах.

Чому: Якщо шлях до підшару зламаний або файл, на який є посилання, не існує, структура шару є неповною або недійсною.

Як: Я використовую SdfLayer::FindOrOpen, щоб перевірити, чи можна вирішити шлях до кожного підшару або посилання. Якщо це не вдається, я реєструю проблему в errors.

4) Перевірка варіантів

Фрагмент коду (спроба вибору варіанту):

if (!varSet.SetVariantSelection(variantName)) {  
 errors.push_back("Failed to set variant '" + variantName +   
 "' in set '" + setName + "' at: " +  
 prim.GetPath().GetString());  
 continue;  
}

Що: Для кожного варіанту в наборі варіантів я намагаюся вибрати його за допомогою SetVariantSelection.

Чому: Я хочу переконатися, що вибір будь-якого варіанту не призведе до поломки прима. Якщо SetVariantSelection не вдається, варіант, ймовірно, зламаний або відсутній.

Як: Після встановлення варіанту я знову отримую прим, щоб переконатися, що він все ще є дійсним.
Якщо ні, я реєструю помилку, щоб користувач знав, що варіант є проблемним.

Розширення можливостей тестування

Під час роботи з USD файлом bad_file я намагався створити тест, який відкриває файл і перевіряє, чи правильно він не проходить всі тести валідації. Однак я стикнувся з серйозними труднощами. Файл спричинив помилки компіляції, через що він не відкривався зовсім. Наприклад, виникла наступна помилка під час виконання:

Runtime Error: in _RaiseErrorPEGTL at line 48 of /builds/omniverse/usd-ci/USD/pxr/usd/sdf/textParserHelpers.h -- /usd-automated-testing/test/bad_file/bad_file.usda:13:9: Expected } at 'variants = {}' in   

Runtime Error: in Open at line 1052 of /builds/omniverse/usd-ci/USD/pxr/usd/usd/stage.cpp -- Failed to open layer /usd-automated-testing/test/bad_file/bad_file.usda@  
Failed to open USD file.

Переконайтесь, що шлях до файлу правильний і файл доступний.

Цей результат є певною мірою корисним, оскільки він вказує на те, що файл є фундаментально пошкодженим і не може бути оброблений. Однак він також виявив обмеження в поточних можливостях інструменту. Ідеально, щоб інструмент міг відкривати навіть серйозно пошкоджені USD файли, оцінювати їх вміст і надавати детальний зворотний зв'язок. Для файлу на кшталт bad_file інструмент мав би виконати всі тести, визначити, що все в файлі є недійсним, і надати комплексний звіт про невдачі. Такий підхід забезпечив би більш детальний та дієвий зворотний зв'язок, порівняно з простим невдалим відкриттям файлу.

Я витратив час на дослідження цієї проблеми, але постійно стикався з помилками компіляції. Це свідчить про те, що, можливо, мені потрібно переосмислити підхід до тестування таких випадків. Наразі поточна поведінка інструменту, коли файли з критичними помилками не відкриваються, є прийнятною.
Однак цей досвід надихнув на ідеї для майбутніх покращень, таких як вдосконалення інструменту для обробки та оцінки файлів, які в іншому випадку не вдалося б завантажити. Це значно розширило б можливості валідації інструменту та надало користувачам глибші відомості про їхні активи.

Підсумки

Поліпшення самого інструменту виявилося складним і об’ємним процесом, тому я віддав перевагу вдосконаленню інструменту та його можливостей для тестування над оновленням Jenkins CI/CD pipeline. Оновлення Jenkins для відображення цих змін виходить за межі цієї фази, але залишається важливим напрямком для майбутнього розвитку. Моя основна мета для частини 3 полягала в тому, щоб зробити інструмент більш надійним і ефективним, залишивши оновлення Jenkins наступним логічним кроком.

Хоча інструмент має великий потенціал для подальшого розвитку, наступним пріоритетом буде оновлення Jenkins CI/CD pipeline для впровадження нещодавніх змін та додаткових тестових файлів.
Це забезпечить повну підтримку розширеного функціоналу інструменту в pipeline.

Версія для швидкого ознайомлення

Нижче наведені основні моменти частини 3 мого проекту інструменту автоматизованого тестування USD:

Покращення CMake

Спрощено управління залежностями шляхом:

  • Перетворення змінної USD_ROOT на кешовану змінну.
  • Організації target_include_directories і target_link_libraries для підтримки модульних зборок.

Адаптовано користувацькі установки USD, узгоджуючи з стандартними практиками для кросплатформного програмного забезпечення.

Спрощена інтеграція Python:

  • Заміна застарілих змінних ${Python3_*} на цілі Python::Python.

Покращення тестового запуску

Додано гнучкість для користувачів за допомогою флагів для:

  • Запуску конкретних тестів (наприклад, -only-geometry).
  • Пропуску певних тестів (наприклад, -skip-shaders).

Забезпечено експорт результатів за допомогою флагу -output, що дозволяє користувачам зберігати детальні логи для подальшого аналізу або обміну.

Розширена валідація

  • Геометрія: Я переконався, що інструмент перевіряє відсутність або некоректні атрибути extent та виявляє дегенеративну геометрію, при цьому коректно обробляє файли без геометрії.
  • Шейдери: Я впевнився, що інструмент перевіряє шейдери на наявність коректних ID, входів та з'єднань, але приймає файли без шейдерів.
  • Структура шарів: Я додав перевірки для правильного defaultPrim в неназначених шарах і валідацію для посилань та підшарів.
  • Варіанти: Я покращив валідацію, тестуючи всі комбінації варіантів, і тепер тести не проходять лише для неправильно визначених наборів або некоректних виборів.

Уроки та плани на майбутнє

Спочатку я не проходив тести для файлів, що не містять геометрії, шейдерів або варіантів.
Я зрозумів, що цей підхід був занадто обмежувальним, оскільки файли можуть бути валідними без цих елементів. Я переглянув свою логіку і тепер тести не фейлятся через відсутність елементів, а лише через явні помилки, що робить інструмент більш адаптивним до реальних USD пайплайнів.

У майбутньому я планую покращити обробку файлів з серйозними помилками, такими як файли з синтаксичними помилками, які не можна відкрити. Також я інтегрую ці можливості в існуючий pipeline Jenkins CI/CD, щоб забезпечити безперервне тестування в відповідності до розширеного функціоналу інструменту.

Перекладено з: USD Automated Testing Tool Functionality Expansion

Leave a Reply

Your email address will not be published. Required fields are marked *