diff --git a/docs/installation.rst b/docs/installation.rst index b17cbbb..b2f011d 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -21,7 +21,6 @@ libraries are available to you: * `GZIP `_ (gzip data compression) * `LZMA `_ (lzma data compression) * `CPPUnit `_ (unit testing) -* `OpenSSL `_ (unit testing; MD5 checksums) .. note:: * The instructions covered in this guide assume you are running a @@ -33,8 +32,7 @@ libraries are available to you: To ensure that all the packages are installed, one can run the following:: sudo apt install build-essential cmake libtclap-dev libboost-all-dev \ - pkg-config libcppunit-dev libeigen3-dev liblzma-dev zlib1g-dev libbz2-dev \ - libssl-dev + pkg-config libcppunit-dev libeigen3-dev liblzma-dev zlib1g-dev libbz2-dev Standard compilation -------------------- diff --git a/src/test/CMakeLists.txt b/src/test/CMakeLists.txt index 302fb99..60a0e72 100644 --- a/src/test/CMakeLists.txt +++ b/src/test/CMakeLists.txt @@ -19,8 +19,6 @@ #*************************************************************************/ pkg_check_modules(CPPUNIT REQUIRED IMPORTED_TARGET cppunit) -pkg_check_modules(CRYPTO REQUIRED IMPORTED_TARGET libcrypto) - add_library(unittest STATIC unittest.cpp) target_compile_features(unittest PUBLIC cxx_std_17) @@ -63,7 +61,6 @@ foreach(testexec ${EXECUTABLES}) unittest den2objsources PkgConfig::CPPUNIT - PkgConfig::CRYPTO ) add_test(NAME ${testexec} COMMAND ${testexec}) diff --git a/src/test/test_file_creation.cpp b/src/test/test_file_creation.cpp index af15639..186694c 100644 --- a/src/test/test_file_creation.cpp +++ b/src/test/test_file_creation.cpp @@ -29,106 +29,134 @@ void TestFileCreation::tearDown() { } void TestFileCreation::test_ply_file() { - // set number of threads to 1 - omp_set_num_threads(1); - - // read scalar field - ScalarField sf("co_2pi_x.cub", ScalarFieldInputFileType::SFF_CUB); - sf.read(); - - // perform marching cubes algorithm - IsoSurface is(&sf); - is.marching_cubes(0.01); - - // construct mesh - IsoSurfaceMesh ism(&sf, &is); - ism.construct_mesh(true); + const auto ref = this->generate_mesh(); + this->assert_ply_shape("test.ply", ref); +} - // create file - ism.write_to_file("test.ply", "co molecule 2pi_x orbital test", "molecule"); +void TestFileCreation::test_stl_file() { + const auto ref = this->generate_mesh(); + this->assert_stl_shape("test.stl", ref); +} - // test md5sum - CPPUNIT_ASSERT_EQUAL(this->md5("test.ply"), std::string("ec143f6ebf9b72761803c3b091f54bf3")); +void TestFileCreation::test_obj_file() { + const auto ref = this->generate_mesh(); + this->assert_obj_shape("test.obj", ref); } -void TestFileCreation::test_stl_file() { +TestFileCreation::MeshReference TestFileCreation::generate_mesh() const { // set number of threads to 1 omp_set_num_threads(1); // read scalar field ScalarField sf("co_2pi_x.cub", ScalarFieldInputFileType::SFF_CUB); sf.read(); - + // perform marching cubes algorithm IsoSurface is(&sf); is.marching_cubes(0.01); - + // construct mesh IsoSurfaceMesh ism(&sf, &is); ism.construct_mesh(true); - // create file + // create files for all supported output formats + ism.write_to_file("test.ply", "co molecule 2pi_x orbital test", "molecule"); ism.write_to_file("test.stl", "co molecule 2pi_x orbital test", "molecule"); + ism.write_to_file("test.obj", "co molecule 2pi_x orbital test", "molecule"); - // test md5sum - CPPUNIT_ASSERT_EQUAL(this->md5("test.stl"), std::string("c2194ba639caf5092654862bb9f93298")); + return { + ism.get_vertices().size(), + ism.get_normals().size(), + ism.get_texcoords().size() / 6 + }; } -void TestFileCreation::test_obj_file() { - // set number of threads to 1 - omp_set_num_threads(1); +void TestFileCreation::assert_obj_shape(const std::string& filename, const MeshReference& ref) const { + CPPUNIT_ASSERT(std::filesystem::exists(filename)); + + std::ifstream infile(filename); + CPPUNIT_ASSERT(infile.good()); + + std::string line; + size_t vertex_lines = 0; + size_t normal_lines = 0; + size_t face_lines = 0; + + while(std::getline(infile, line)) { + if(line.rfind("v ", 0) == 0) { + vertex_lines++; + } else if(line.rfind("vn ", 0) == 0) { + normal_lines++; + } else if(line.rfind("f ", 0) == 0) { + face_lines++; + } + } - // read scalar field - ScalarField sf("co_2pi_x.cub", ScalarFieldInputFileType::SFF_CUB); - sf.read(); - - // perform marching cubes algorithm - IsoSurface is(&sf); - is.marching_cubes(0.01); - - // construct mesh - IsoSurfaceMesh ism(&sf, &is); - ism.construct_mesh(true); + CPPUNIT_ASSERT_EQUAL(ref.vertices, vertex_lines); + CPPUNIT_ASSERT_EQUAL(ref.normals, normal_lines); + CPPUNIT_ASSERT_EQUAL(ref.triangles, face_lines); +} - // create file - ism.write_to_file("test.obj", "co molecule 2pi_x orbital test", "molecule"); +void TestFileCreation::assert_ply_shape(const std::string& filename, const MeshReference& ref) const { + CPPUNIT_ASSERT(std::filesystem::exists(filename)); + + std::ifstream infile(filename, std::ios::binary); + CPPUNIT_ASSERT(infile.good()); + + std::string line; + size_t header_size = 0; + size_t header_vertices = 0; + size_t header_faces = 0; + bool seen_ply = false; + bool seen_binary_format = false; + + while(std::getline(infile, line)) { + header_size += line.size() + 1; + + if(line == "ply") { + seen_ply = true; + } + if(line == "format binary_little_endian 1.0" || line == "format binary_big_endian 1.0") { + seen_binary_format = true; + } + if(line.rfind("element vertex ", 0) == 0) { + header_vertices = std::stoul(line.substr(15)); + } + if(line.rfind("element face ", 0) == 0) { + header_faces = std::stoul(line.substr(13)); + } + if(line == "end_header") { + break; + } + } + + CPPUNIT_ASSERT(seen_ply); + CPPUNIT_ASSERT(seen_binary_format); + CPPUNIT_ASSERT_EQUAL(ref.vertices, header_vertices); + CPPUNIT_ASSERT_EQUAL(ref.triangles, header_faces); - // test md5sum - CPPUNIT_ASSERT_EQUAL(this->md5("test.obj"), std::string("e2b3e09f9c010dac99a7bc0137c187ec")); + const size_t expected_binary_size = + ref.vertices * (6 * sizeof(float)) + + ref.triangles * (sizeof(uint8_t) + 3 * sizeof(unsigned int)); + + CPPUNIT_ASSERT_EQUAL(header_size + expected_binary_size, (size_t)std::filesystem::file_size(filename)); } -/** - * @brief Calculate MD5 checksum of a file - * - * @param[in] name Path to the file - * - * @return 32 byte string containing md5 checksum - */ -std::string TestFileCreation::md5(const std::string& filename) { - // read the file - std::ifstream mfile(filename, std::ios::binary | std::ios::ate); - std::streamsize size = mfile.tellg(); - mfile.seekg(0, std::ios::beg); - char buffer[size]; - mfile.read(buffer, size); - mfile.close(); - - // output variable for hash - unsigned char hash[MD5_DIGEST_LENGTH]; - - // calculate the md5 hash - EVP_MD_CTX *ctx = EVP_MD_CTX_create(); - EVP_MD_CTX_init(ctx); - const EVP_MD *md_type = EVP_md5(); - EVP_DigestInit_ex(ctx, md_type, NULL); - EVP_DigestUpdate(ctx, buffer, size); - EVP_DigestFinal_ex(ctx, hash, NULL); - EVP_MD_CTX_destroy(ctx); - - // output as a 32-byte hex-string - std::stringstream ss; - for(int i = 0; i < MD5_DIGEST_LENGTH; i++){ - ss << std::hex << std::setw(2) << std::setfill('0') << static_cast( hash[i] ); - } - return ss.str(); -} \ No newline at end of file +void TestFileCreation::assert_stl_shape(const std::string& filename, const MeshReference& ref) const { + CPPUNIT_ASSERT(std::filesystem::exists(filename)); + + std::ifstream infile(filename, std::ios::binary); + CPPUNIT_ASSERT(infile.good()); + + // skip header + infile.seekg(80, std::ios::beg); + + uint32_t triangle_count = 0; + infile.read(reinterpret_cast(&triangle_count), sizeof(uint32_t)); + CPPUNIT_ASSERT(infile.good()); + + CPPUNIT_ASSERT_EQUAL(static_cast(ref.triangles), triangle_count); + + const size_t expected_size = 80 + sizeof(uint32_t) + ref.triangles * (12 * sizeof(float) + sizeof(uint16_t)); + CPPUNIT_ASSERT_EQUAL(expected_size, (size_t)std::filesystem::file_size(filename)); +} diff --git a/src/test/test_file_creation.h b/src/test/test_file_creation.h index 3615fd2..744ab0f 100644 --- a/src/test/test_file_creation.h +++ b/src/test/test_file_creation.h @@ -23,10 +23,9 @@ #include -// we need these for the MD5 checksums -#include -#include -#include +#include +#include +#include #include "scalar_field.h" #include "isosurface.h" @@ -34,12 +33,11 @@ /** * Test that verifies file creation (obj, stl and ply) - * + * * Because the marching cubes algorithm uses OpenMP parallellization, we need * to set the number of threads to 1 to obtain consistent results. With * higher number of cores, the results are subject to race conditions, leading - * to different (although not incorrect) results preventing the use of a simple - * MD5 checksum for file validation. + * to different (although not incorrect) results. */ class TestFileCreation : public CppUnit::TestFixture { CPPUNIT_TEST_SUITE( TestFileCreation ); @@ -56,7 +54,16 @@ class TestFileCreation : public CppUnit::TestFixture { void tearDown(); private: - std::string md5(const std::string &str); + struct MeshReference { + size_t vertices; + size_t normals; + size_t triangles; + }; + + MeshReference generate_mesh() const; + void assert_obj_shape(const std::string& filename, const MeshReference& ref) const; + void assert_ply_shape(const std::string& filename, const MeshReference& ref) const; + void assert_stl_shape(const std::string& filename, const MeshReference& ref) const; }; -#endif \ No newline at end of file +#endif