Skip to content
Closed
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
4 changes: 1 addition & 3 deletions docs/installation.rst
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ libraries are available to you:
* `GZIP <https://www.gnu.org/software/gzip/>`_ (gzip data compression)
* `LZMA <https://7-zip.org/>`_ (lzma data compression)
* `CPPUnit <https://sourceforge.net/projects/cppunit/>`_ (unit testing)
* `OpenSSL <https://www.openssl.org/>`_ (unit testing; MD5 checksums)

.. note::
* The instructions covered in this guide assume you are running a
Expand All @@ -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
--------------------
Expand Down
3 changes: 0 additions & 3 deletions src/test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down Expand Up @@ -63,7 +61,6 @@ foreach(testexec ${EXECUTABLES})
unittest
den2objsources
PkgConfig::CPPUNIT
PkgConfig::CRYPTO
)

add_test(NAME ${testexec} COMMAND ${testexec})
Expand Down
182 changes: 105 additions & 77 deletions src/test/test_file_creation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<int>( hash[i] );
}
return ss.str();
}
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<char*>(&triangle_count), sizeof(uint32_t));
CPPUNIT_ASSERT(infile.good());

CPPUNIT_ASSERT_EQUAL(static_cast<uint32_t>(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));
}
25 changes: 16 additions & 9 deletions src/test/test_file_creation.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,23 +23,21 @@

#include <cppunit/extensions/HelperMacros.h>

// we need these for the MD5 checksums
#include <iomanip>
#include <openssl/md5.h>
#include <openssl/evp.h>
#include <cstdint>
#include <filesystem>
#include <string>

#include "scalar_field.h"
#include "isosurface.h"
#include "isosurface_mesh.h"

/**
* 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 );
Expand All @@ -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
#endif
Loading