Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add yaml support for tests and glTF tests #387

Merged
merged 7 commits into from
Jul 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,9 @@ exts/cesium.omniverse/certs/cacert.pem
# Test output
_testoutput

# Test utils header has components generated by CMake
tests/testUtils.h

# Packman user files
*.user

Expand Down
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -425,6 +425,7 @@ find_package(cpr)
find_package(doctest)
find_package(stb)
find_package(ZLIB)
find_package(yaml-cpp)

# So that the installed libraries can find shared libraries in the same directory
set(CMAKE_INSTALL_RPATH $ORIGIN)
Expand Down
9 changes: 9 additions & 0 deletions ThirdParty.extra.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,14 @@
],
"version": "0.15.0",
"url": "https://github.com/CesiumGS/cesium-native"
},
{
"name": "glTF-Asset-Generator",
"license": [
"MIT"
],
"version": "0.6.1",
"url": "https://github.com/KhronosGroup/glTF-Asset-Generator",
"notes": "A selection of test models from this generator are used for testing."
}
]
17 changes: 17 additions & 0 deletions ThirdParty.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,15 @@
"version": "1.9.0",
"url": "https://docs.libcpr.org/"
},
{
"name": "glTF-Asset-Generator",
"license": [
"MIT"
],
"version": "0.6.1",
"url": "https://github.com/KhronosGroup/glTF-Asset-Generator",
"notes": "A selection of test models from this generator are used for testing."
},
{
"name": "libcurl",
"license": [
Expand Down Expand Up @@ -56,6 +65,14 @@
"version": "cci.20220909",
"url": "https://github.com/nothings/stb"
},
{
"name": "yaml-cpp",
"license": [
"MIT"
],
"version": "0.7.0",
"url": "https://github.com/jbeder/yaml-cpp"
},
{
"name": "zlib",
"license": [
Expand Down
1 change: 1 addition & 0 deletions cmake/AddConanDependencies.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ set(REQUIRES
"pybind11/2.10.1@#561736204506dad955276aaab438aab4"
"stb/cci.20220909@#1c47474f095ef8cd9e4959558525b827"
"zlib/1.2.13@#13c96f538b52e1600c40b88994de240f"
"yaml-cpp/0.7.0@#85b409c274a53d226b71f1bdb9cb4f8b"
"libcurl/7.86.0@#88506b3234d553b90af1ceefc3dd1652"
"nasm/2.15.05@#799d63b1672a337584b09635b0f22fc1")

Expand Down
2 changes: 1 addition & 1 deletion src/core/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,11 @@ setup_lib(
CesiumUsdSchemas
CesiumIonClient
Cesium3DTilesSelection
CesiumAsync
CesiumGeospatial
CesiumGeometry
CesiumGltf
CesiumGltfReader
CesiumAsync
CesiumJsonReader
CesiumUtility
async++
Expand Down
5 changes: 5 additions & 0 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@ include(Macros)

glob_files(SOURCES "${CMAKE_CURRENT_LIST_DIR}/*.cpp")

# replace a string in the utils header with the intended working dir for the test executable
set(TEST_WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}")
configure_file("${CMAKE_CURRENT_LIST_DIR}/testUtils.h.in" "${CMAKE_CURRENT_LIST_DIR}/testUtils.h")
mattelser marked this conversation as resolved.
Show resolved Hide resolved

# cmake-format: off
setup_app(
TARGET_NAME
Expand All @@ -11,6 +15,7 @@ setup_app(
LIBRARIES
CesiumOmniverseCore
doctest::doctest
yaml-cpp::yaml-cpp
CXX_FLAGS
${CESIUM_OMNI_CXX_FLAGS}
CXX_FLAGS_DEBUG
Expand Down
58 changes: 57 additions & 1 deletion tests/ExampleTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,27 @@
* A collection of simple tests to demonstrate Doctest
*/

#include "doctestUtils.h"
#include "testUtils.h"

#include <doctest/doctest.h>

#include <array>
#include <cstdint>
#include <iostream>
#include <list>
#include <stdexcept>
#include <vector>

#include <yaml-cpp/yaml.h>

const std::string CONFIG_PATH = "tests/configs/exampleConfig.yaml";

// Test Suites are not required, but this sort of grouping makes it possible
// to select which tests do/don't run via command line options
TEST_SUITE("Example Tests") {
// ----------------------------------------------
// Basic Tests
// ----------------------------------------------

TEST_CASE("The most basic test") {
CHECK(1 + 1 == 2);
Expand Down Expand Up @@ -65,6 +75,52 @@ TEST_SUITE("Example Tests") {
CHECK(item > 0);
}

// ----------------------------------------------
// YAML Config Examples
// ----------------------------------------------

std::string transmogrifier(const std::string& s) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I love the name.

// an example function with differing output for some scenarios
if (s == "scenario2") {
return "bar";
}
return "foo";
}

void checkAgainstExpectedResults(const std::string& scenarioName, const YAML::Node& expectedResults) {

// we have to specify the type of the desired data from the config via as()
CHECK(3.14159 == expectedResults["pi"].as<double>());
CHECK(2 == expectedResults["onlyEvenPrime"].as<int>());

// as() does work for some non-scalar types, such as vectors, lists, and maps
// for adding custom types to the config, see:
// https://github.com/jbeder/yaml-cpp/wiki/Tutorial#converting-tofrom-native-data-types
const auto fib = expectedResults["fibonacciSeq"].as<std::vector<int>>();
CHECK(fib[2] + fib[3] == fib[4]);

// More complicated checks can be done with helper functions that take the scenario as input
CHECK(transmogrifier(scenarioName) == expectedResults["transmogrifierOutput"].as<std::string>());
}

TEST_CASE("Use a config file to detail multiple scenarios") {

YAML::Node configRoot = YAML::LoadFile(CONFIG_PATH);

// The config file has default parameters and
// an override for one or more scenarios
std::vector<std::string> scenarios = {"scenario1", "scenario2", "scenario3"};

for (const auto& s : scenarios) {
ConfigMap conf = getScenarioConfig(s, configRoot);
checkAgainstExpectedResults(s, conf);
}
}

// ----------------------------------------------
// Misc.
// ----------------------------------------------

TEST_CASE("A few other useful macros") {
// The most common test macro is CHECK, but others are available
// Here are just a few
Expand Down
149 changes: 149 additions & 0 deletions tests/GltfTests.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
#include "testUtils.h"

#include "cesium/omniverse/GltfAccessors.h"
#include "cesium/omniverse/GltfUtil.h"

#include <CesiumGltf/Material.h>
#include <CesiumGltf/MeshPrimitive.h>
#include <CesiumGltf/Model.h>
#include <CesiumGltfReader/GltfReader.h>
#include <doctest/doctest.h>

#include <cstddef>
#include <cstdio>
#include <filesystem>
#include <fstream>
#include <iostream>
#include <optional>
#include <stdexcept>
#include <string>
#include <vector>

#include <gsl/span>
#include <yaml-cpp/yaml.h>

using namespace cesium::omniverse;

const std::string ASSET_DIR = "tests/testAssets/gltfs";
const std::string CONFIG_PATH = "tests/configs/gltfConfig.yaml";

// simplifies casting when comparing some material queries to expected output from config
bool operator==(const pxr::GfVec3f& v3, const std::vector<float>& v) {
return v.size() == 3 && v3[0] == v[0] && v3[1] == v[1] && v3[2] == v[2];
}

TEST_SUITE("Test GltfUtil") {
void checkGltfExpectedResults(const std::filesystem::path& gltfFileName, const YAML::Node& expectedResults) {

// --- Load Gltf ---
std::ifstream gltfStream(gltfFileName, std::ifstream::binary);
gltfStream.seekg(0, std::ios::end);
auto gltfFileLength = gltfStream.tellg();
gltfStream.seekg(0, std::ios::beg);

std::vector<std::byte> gltfBuf(gltfFileLength);
gltfStream.read((char*)&gltfBuf[0], gltfFileLength);

CesiumGltfReader::GltfReader reader;
auto gltf = reader.readGltf(gsl::span(reinterpret_cast<const std::byte*>(gltfBuf.data()), gltfFileLength));

if (!gltf.errors.empty()) {
for (const auto& err : gltf.errors) {
std::cerr << err;
}
throw std::runtime_error("failed to parse model");
}

// gltf.model is a std::optional<CesiumGltf::Model>, make sure it exists
if (!(gltf.model && gltf.model->meshes.size() > 0)) {
throw std::runtime_error("test model is empty");
}

// --- Begin checks ---
const auto& prim = gltf.model->meshes[0].primitives[0];
const auto& model = *gltf.model;

CHECK(GltfUtil::hasNormals(model, prim, false) == expectedResults["hasNormals"].as<bool>());
CHECK(GltfUtil::hasTexcoords(model, prim, 0) == expectedResults["hasTexcoords"].as<bool>());
CHECK(GltfUtil::hasImageryTexcoords(model, prim, 0) == expectedResults["hasImageryTexcoords"].as<bool>());
CHECK(GltfUtil::hasVertexColors(model, prim, 0) == expectedResults["hasVertexColors"].as<bool>());
CHECK(GltfUtil::hasMaterial(prim) == expectedResults["hasMaterial"].as<bool>());
CHECK(GltfUtil::getDoubleSided(model, prim) == expectedResults["doubleSided"].as<bool>());

// material tests
if (GltfUtil::hasMaterial(prim)) {
const auto& mat = gltf.model->materials[0];
CHECK(GltfUtil::getAlphaMode(mat) == expectedResults["alphaMode"].as<int>());
CHECK(GltfUtil::getAlphaCutoff(mat) == expectedResults["alphaCutoff"].as<float>());
CHECK(GltfUtil::getBaseAlpha(mat) == expectedResults["baseAlpha"].as<float>());
CHECK(GltfUtil::getMetallicFactor(mat) == expectedResults["metallicFactor"].as<float>());
CHECK(GltfUtil::getRoughnessFactor(mat) == expectedResults["roughnessFactor"].as<float>());
CHECK(GltfUtil::getBaseColorTextureWrapS(model, mat) == expectedResults["baseColorTextureWrapS"].as<int>());
CHECK(GltfUtil::getBaseColorTextureWrapT(model, mat) == expectedResults["baseColorTextureWrapT"].as<int>());

CHECK(GltfUtil::getBaseColorFactor(mat) == expectedResults["baseColorFactor"].as<std::vector<float>>());
CHECK(GltfUtil::getEmissiveFactor(mat) == expectedResults["emissiveFactor"].as<std::vector<float>>());
}

// Accessor smoke tests
PositionsAccessor positions;
IndicesAccessor indices;
positions = GltfUtil::getPositions(model, prim);
CHECK(positions.size() > 0);
indices = GltfUtil::getIndices(model, prim, positions);
CHECK(indices.size() > 0);
if (GltfUtil::hasNormals(model, prim, false)) {
CHECK(GltfUtil::getNormals(model, prim, positions, indices, false).size() > 0);
}
if (GltfUtil::hasVertexColors(model, prim, 0)) {
CHECK(GltfUtil::getVertexColors(model, prim, 0).size() > 0);
}
if (GltfUtil::hasTexcoords(model, prim, 0)) {
CHECK(GltfUtil::getTexcoords(model, prim, 0).size() > 0);
}
if (GltfUtil::hasImageryTexcoords(model, prim, 0)) {
CHECK(GltfUtil::getImageryTexcoords(model, prim, 0).size() > 0);
}
CHECK(GltfUtil::getExtent(model, prim) != std::nullopt);
}

TEST_CASE("Default getter smoke tests") {

CHECK_NOTHROW(GltfUtil::getDefaultBaseAlpha());
CHECK_NOTHROW(GltfUtil::getDefaultBaseColorFactor());
CHECK_NOTHROW(GltfUtil::getDefaultMetallicFactor());
CHECK_NOTHROW(GltfUtil::getDefaultRoughnessFactor());
CHECK_NOTHROW(GltfUtil::getDefaultEmissiveFactor());
CHECK_NOTHROW(GltfUtil::getDefaultWrapS());
CHECK_NOTHROW(GltfUtil::getDefaultWrapT());
CHECK_NOTHROW(GltfUtil::getDefaultAlphaCutoff());
CHECK_NOTHROW(GltfUtil::getDefaultAlphaMode());
}

TEST_CASE("Check helper functions on various models") {

std::vector<std::string> gltfFiles;

// get list of gltf test files
for (auto const& i : std::filesystem::directory_iterator(ASSET_DIR)) {
std::filesystem::path fname = i.path().filename();
if (fname.extension() == ".gltf" || fname.extension() == ".glb") {
gltfFiles.push_back(fname.string());
}
}
Comment on lines +125 to +133
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think gltfFiles could be a vector of std::filesystem::path instead

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it eventually leads to a string based key/value lookup in the broader testUtils, which uses std::string to be more general than assuming all scenario names will be file paths. I can certainly stringify the path later in this function, but I think it needs to become a string before getScenarioConfig reads it.


// parse test config yaml
const auto configRoot = YAML::LoadFile(CONFIG_PATH);
const auto basePath = std::filesystem::path(ASSET_DIR);

for (auto const& fileName : gltfFiles) {
// attach filename to any failed checks
CAPTURE(fileName);

const auto conf = getScenarioConfig(fileName, configRoot);

// the / operator concatonates file paths
checkGltfExpectedResults(basePath / fileName, conf);
}
}
}
3 changes: 1 addition & 2 deletions tests/ObjectPoolTests.cpp
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
#include "doctestUtils.h"
#include "testUtils.h"

#include <cesium/omniverse/ObjectPool.h>
#include <doctest/doctest.h>
#include <sys/types.h>

#include <algorithm>
#include <cstdint>
Expand Down
29 changes: 29 additions & 0 deletions tests/configs/exampleConfig.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
---


# One way to use the config file is to generate multiple scenarios with
# variations on some known parameters. If most of the scenarios have a
# parameter at one particular value, it can make sense to establish that as
# the default, then we only need to list the changes from that default.
# See the gltf test config for a real use-case
scenarios:
default:
# currently supported data types for the testUtils methods:
# float
pi : 3.14159
# int
onlyEvenPrime : 2
# string
transmogrifierOutput : "foo"
# sequence
fibonacciSeq :
- 1
- 1
- 2
- 3
- 5


# an example override for a given item
scenario2:
transmogrifierOutput : "bar"
Loading