diff --git a/.travis.yml b/.travis.yml index 69f29f6..0551ffe 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,15 +3,15 @@ language: python env: global: - EPANET_HOME=`pwd` - - BUILD_HOME=buildproducts + - BUILD_HOME=buildprod - TEST_HOME=tests/epanet-nrtestsuite before_install: - sudo apt-get -qq update + - sudo apt-get install -y libboost-test-dev - sudo apt-get install -y swig -install: - - pip install --src build/packages -r tools/requirements.txt +#install: before_script: - mkdir -p $BUILD_HOME @@ -19,7 +19,12 @@ before_script: - cmake .. script: - - make + - cmake --build . + # run unit tests + - cd tests + - ctest + # run regression tests - cd $EPANET_HOME + - pip install -r tools/requirements.txt - tools/gen-config.sh $EPANET_HOME/$BUILD_HOME/bin > $TEST_HOME/apps/epanet-$TRAVIS_COMMIT.json - tools/run-nrtest.sh $TEST_HOME $TRAVIS_COMMIT diff --git a/CMakeLists.txt b/CMakeLists.txt index 787806c..808b836 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -41,11 +41,19 @@ cmake_minimum_required (VERSION 2.8.8) project(EPANET) +add_subdirectory(tools/epanet-output) +add_subdirectory(tests) -SET(EXECUTABLE_OUTPUT_PATH ${CMAKE_BINARY_DIR}/bin) -SET(LIBRARY_OUTPUT_PATH ${CMAKE_BINARY_DIR}/bin) + +# Sets for output directory for executables and libraries. +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) + +# Sets the position independent code property for all targets SET(CMAKE_POSITION_INDEPENDENT_CODE ON) + IF (APPLE) SET(CMAKE_INSTALL_NAME_DIR @executable_path) SET(CMAKE_BUILD_WITH_INSTALL_RPATH ON) @@ -56,20 +64,35 @@ IF (MSVC) add_definitions(-D_CRT_SECURE_NO_DEPRECATE) ENDIF (MSVC) -# create object library for reuse in other targets -include_directories(include src) + +#include_directories(include src) + # configure file groups file(GLOB EPANET_SOURCES src/*.c) -set(EPANET_API_HEADER include/epanet2.h) +#set(EPANET_API_HEADER include/epanet2.h) set(EPANET_CLI_SOURCES run/main.c) file(GLOB EPANET_LIB_ALL src/*) source_group("Library" FILES ${EPANET_LIB_ALL}) source_group("CLI" REGULAR_EXPRESSION "run/.*") + # the shared library -add_library(epanet SHARED ${EPANET_SOURCES} ${EPANET_API_HEADER}) +add_library(epanet SHARED ${EPANET_SOURCES}) #${EPANET_API_HEADER}) +target_include_directories(epanet PUBLIC ${PROJECT_SOURCE_DIR}/include) + +# create export lib so we can link against dll using Visual Studio +#include(GenerateExportHeader) +#GENERATE_EXPORT_HEADER(epanet +# BASE_NAME epanet +# EXPORT_MACRO_NAME DLLEXPORT +# EXPORT_FILE_NAME epanet_export.h +# STATIC_DEFINE SHARED_EXPORTS_BUILT_AS_STATIC) +# +#file(COPY ${CMAKE_CURRENT_BINARY_DIR}/epanet_export.h +# DESTINATION ${CMAKE_CURRENT_SOURCE_DIR}/include) + # the standalone executable add_executable(runepanet ${EPANET_CLI_SOURCES}) diff --git a/appveyor.yml b/appveyor.yml index 42eece6..56ef02d 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -7,7 +7,7 @@ # US EPA - ORD/NRMRL # -version: 2.0.{build} +version: 2.0.{build} image: - Visual Studio 2013 @@ -17,32 +17,42 @@ init: - set EPANET_HOME=%APPVEYOR_BUILD_FOLDER% - set BUILD_HOME=buildprod - set TEST_HOME=tests\epanet-nrtestsuite - - set NRTEST_SCRIPT=%EPANET_HOME%\%BUILD_HOME%\packages\nrtest\scripts + - set NRTEST_SCRIPT=C:\Python27\Scripts - set GENERATOR="Visual Studio 10 2010" - -cache: - - C:\ProgramData\chocolatey\bin -> appveyor.yml - - C:\ProgramData\chocolatey\lib -> appveyor.yml - - '%BUILD_HOME% -> CMakeLists.txt' + - set BOOST_ROOT="C:\\Libraries\\boost" + - set BOOST_LIB="C:\\Libraries\\boost\\lib32-msvc-12.0" # called after repo clone install: - choco install swig - - python -m pip install --src %BUILD_HOME%\packages -r tools\requirements.txt # called before build before_build: + - mkdir %BUILD_HOME% - cd %BUILD_HOME% - - cmake -G %GENERATOR% -DCMAKE_BUILD_TYPE=Release .. + - cmake -G %GENERATOR% + -DBOOST_ROOT="%BOOST_ROOT%" + -DBOOST_LIBRARYDIR="%BOOST_LIB%" + -DBoost_USE_STATIC_LIBS="ON" .. # run custom build script build_script: - - cmake --build . --target runepanet --config Release + - cmake --build . --config Release before_test: - cd %EPANET_HOME% + - python -m pip install -r tools\requirements.txt - tools\gen-config.cmd %EPANET_HOME%\%BUILD_HOME%\bin\Release > %TEST_HOME%\apps\epanet-%APPVEYOR_REPO_COMMIT%.json # run custom test script test_script: + # run unit tests + - cd %BUILD_HOME%\tests + - ctest -C Release + # run regression tests + - cd %EPANET_HOME% - tools\run-nrtest.cmd %NRTEST_SCRIPT% %TEST_HOME% %APPVEYOR_REPO_COMMIT% + +cache: + - C:\ProgramData\chocolatey\bin -> appveyor.yml + - C:\ProgramData\chocolatey\lib -> appveyor.yml diff --git a/include/epanet2.h b/include/epanet2.h index 55250f8..ebd367b 100644 --- a/include/epanet2.h +++ b/include/epanet2.h @@ -60,6 +60,7 @@ #endif #endif +//#include "epanet_export.h" // --- Define the EPANET toolkit constants diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt new file mode 100644 index 0000000..609d0b4 --- /dev/null +++ b/tests/CMakeLists.txt @@ -0,0 +1,46 @@ +# +# CMakeLists.txt - CMake configuration file for epanet/tests +# +# Created: February 13, 2018 +# Author: Constantin Savtchenko +# Ref: http://neyasystems.com/an-engineers-guide-to-unit-testing-cmake-and-boost-unit-tests/ +# +# Modified by: Michael E. Tryby +# US EPA ORD/NRMRL +# + +#Setup CMake to run tests +enable_testing() + + +# Sets for output directory for executables and libraries. +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) + + +#Prep ourselves for compiling boost +find_package(Boost REQUIRED) +include_directories (${Boost_INCLUDE_DIRS}) + + +#I like to keep test files in a separate source directory called test +file(GLOB TEST_SRCS RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} test_*.cpp) + + +#Run through each source +foreach(testSrc ${TEST_SRCS}) + #Extract the filename without an extension (NAME_WE) + get_filename_component(testName ${testSrc} NAME_WE) + + #Add compile target + add_executable(${testName} ${testSrc}) + + #link to Boost libraries AND your targets and dependencies + target_link_libraries(${testName} ${Boost_LIBRARIES} epanet epanet-output) + + + #Finally add it to test execution - + #Notice the WORKING_DIRECTORY and COMMAND + add_test(NAME ${testName} + COMMAND ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${testName} + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/data) +endforeach(testSrc) diff --git a/tests/data/net1.out b/tests/data/net1.out new file mode 100644 index 0000000..6cede26 Binary files /dev/null and b/tests/data/net1.out differ diff --git a/tests/test_output.cpp b/tests/test_output.cpp new file mode 100644 index 0000000..cd55f4f --- /dev/null +++ b/tests/test_output.cpp @@ -0,0 +1,358 @@ +/* + * test_epanet_output.cpp + * + * Created: 8/4/2017 + * Author: Michael E. Tryby + * US EPA - ORD/NRMRL + * + * Unit testing for EPANET Output API. +*/ + +// NOTE: Travis installs libboost test version 1.5.4 +// NOTE: Can not dyn link boost using Visual Studio 10 2010 +//#define BOOST_TEST_DYN_LINK + +#define BOOST_TEST_MODULE "output" +#include + +#include +#include +#include +#include + +#include "epanet_output.h" + + +#define DATA_PATH "./net1.out" + +using namespace std; + +// Custom test to check the minimum number of correct decimal digits between +// the test and the ref vectors. +boost::test_tools::predicate_result check_cdd(std::vector& test, + std::vector& ref, long cdd_tol) +{ + float tmp, min_cdd = 100.0; + + // TODO: What is the vectors aren't the same length? + + std::vector::iterator test_it; + std::vector::iterator ref_it; + + for (test_it = test.begin(); test_it < test.end(); ++test_it) { + for (ref_it = ref.begin(); ref_it < ref.end(); ++ref_it) { + + if (*test_it != *ref_it) { + tmp = - log10f(abs(*test_it - *ref_it)); + if (tmp < min_cdd) min_cdd = tmp; + } + } + } + + if (min_cdd == 100.0) + return true; + else + return floor(min_cdd) <= cdd_tol; +} + +boost::test_tools::predicate_result check_string(std::string test, std::string ref) +{ + if (ref.compare(test) == 0) + return true; + else + return false; +} + +BOOST_AUTO_TEST_SUITE (test_output_auto) + +BOOST_AUTO_TEST_CASE(InitTest) { + ENR_Handle p_handle = NULL; + + int error = ENR_init(&p_handle); + BOOST_REQUIRE(error == 0); + BOOST_CHECK(p_handle != NULL); +} + +BOOST_AUTO_TEST_CASE(OpenTest) { + std::string path = std::string(DATA_PATH); + ENR_Handle p_handle = NULL; + ENR_init(&p_handle); + + int error = ENR_open(p_handle, path.c_str()); + BOOST_REQUIRE(error == 0); + ENR_close(&p_handle); +} + +BOOST_AUTO_TEST_CASE(CloseTest) { + ENR_Handle p_handle = NULL; + int error = ENR_init(&p_handle); + + error = ENR_close(&p_handle); + BOOST_REQUIRE(error == -1); + BOOST_CHECK(p_handle != NULL); +} + +BOOST_AUTO_TEST_SUITE_END() + + +struct Fixture{ + Fixture() { + path = std::string(DATA_PATH); + + error = ENR_init(&p_handle); + ENR_clearError(p_handle); + error = ENR_open(p_handle, path.c_str()); + + array = NULL; + array_dim = 0; + } + ~Fixture() { + ENR_free((void**)&array); + error = ENR_close(&p_handle); + } + + std::string path; + int error; + ENR_Handle p_handle; + + float* array; + int array_dim; + +}; + +BOOST_AUTO_TEST_SUITE(test_output_fixture) + +BOOST_FIXTURE_TEST_CASE(test_getNetSize, Fixture) +{ + int *i_array = NULL; + + error = ENR_getNetSize(p_handle, &i_array, &array_dim); + BOOST_REQUIRE(error == 0); + + // nodes, tanks, links, pumps, valves + std::vector test; + test.assign(i_array, i_array + array_dim); + + const int ref_dim = 5; + int ref_array[ref_dim] = {11,2,13,1,0}; + + std::vector ref; + ref.assign(ref_array, ref_array + ref_dim); + + BOOST_CHECK_EQUAL_COLLECTIONS(ref.begin(), ref.end(), test.begin(), test.end()); + + ENR_free((void**)&i_array); +} + +BOOST_FIXTURE_TEST_CASE(test_getElementName, Fixture) { + char* name = new char[MAXID]; + int length, index = 1; + + error = ENR_getElementName(p_handle, ENR_node, index, &name, &length); + BOOST_REQUIRE(error == 0); + + std::string test (name); + std::string ref ("10"); + BOOST_CHECK(check_string(test, ref)); + + delete(name); +} + +BOOST_FIXTURE_TEST_CASE(test_getNodeAttribute, Fixture) { + + error = ENR_getNodeAttribute(p_handle, 1, ENR_quality, &array, &array_dim); + BOOST_REQUIRE(error == 0); + + const int ref_dim = 11; + float ref_array[ref_dim] = { 1.0f, + 0.44407997f, + 0.43766347f, + 0.42827705f, + 0.41342604f, + 0.42804748f, + 0.44152543f, + 0.40502965f, + 0.38635802f, + 1.0f, + 0.96745253f}; + + std::vector ref_vec; + ref_vec.assign(ref_array, ref_array + ref_dim); + + + std::vector test_vec; + test_vec.assign(array, array + array_dim); + + BOOST_CHECK(check_cdd(test_vec, ref_vec, 3)); +} + +BOOST_FIXTURE_TEST_CASE(test_getLinkAttribute, Fixture) { + + error = ENR_getLinkAttribute(p_handle, 1, ENR_flow, &array ,&array_dim); + BOOST_REQUIRE(error == 0); + + const int ref_dim = 13; + float ref_array[ref_dim] = { 1848.5812f, + 1220.4274f, + 130.11162f, + 187.6893f, + 119.8884f, + 40.464489f, + -748.58112f, + 478.15378f, + 191.73459f, + 30.111609f, + 140.46449f, + 59.535515f, + 1848.5812f}; + + std::vector ref_vec; + ref_vec.assign(ref_array, ref_array + ref_dim); + + std::vector test_vec; + test_vec.assign(array, array + array_dim); + + BOOST_CHECK(check_cdd(test_vec, ref_vec, 3)); +} + +BOOST_FIXTURE_TEST_CASE(test_getNodeResult, Fixture) { + + error = ENR_getNodeResult(p_handle, 1, 2, &array, &array_dim); + BOOST_REQUIRE(error == 0); + + const int ref_dim = 4; + float ref_array[ref_dim] = {0.041142918f, + 150.0f, + 987.98358f, + 120.45029f}; + + std::vector ref_vec; + ref_vec.assign(ref_array, ref_array + ref_dim); + + std::vector test_vec; + test_vec.assign(array, array + array_dim); + + BOOST_CHECK(check_cdd(test_vec, ref_vec, 3)); +} + +BOOST_FIXTURE_TEST_CASE(test_getLinkResult, Fixture) { + + error = ENR_getLinkResult(p_handle, 24, 13, &array, &array_dim); + BOOST_REQUIRE(error == 0); + + const int ref_dim = 8; + float ref_array[ref_dim] = {0.58586824f, + 1892.2433f, + 0.0f, + -200.71875f, + 1.0f, + 3.0f, + 1.0f, + 0.0f}; + + std::vector ref_vec; + ref_vec.assign(ref_array, ref_array + ref_dim); + + std::vector test_vec; + test_vec.assign(array, array + array_dim); + + BOOST_CHECK(check_cdd(test_vec, ref_vec, 3)); +} + +BOOST_FIXTURE_TEST_CASE(test_getNodeSeries, Fixture){ + + error = ENR_getNodeSeries(p_handle, 2, ENR_pressure, 0, 10, &array, &array_dim); + BOOST_REQUIRE(error == 0); + + const int ref_dim = 10; + float ref_array[ref_dim] = {119.25731f, + 120.45029f, + 121.19854f, + 122.00622f, + 122.37414f, + 122.8122f, + 122.82034f, + 122.90379f, + 123.40434f, + 123.81807f}; + + std::vector ref_vec; + ref_vec.assign(ref_array, ref_array + ref_dim); + + std::vector test_vec; + test_vec.assign(array, array + array_dim); + + BOOST_CHECK(check_cdd(test_vec, ref_vec, 3)); +} + +BOOST_FIXTURE_TEST_CASE(test_getLinkSeries, Fixture) { + + error = ENR_getLinkSeries(p_handle, 2, ENR_flow, 0, 10, &array, &array_dim); + BOOST_REQUIRE(error == 0); + + const int ref_dim = 10; + float ref_array[ref_dim] = {1234.2072f, + 1220.4274f, + 1164.4f, + 1154.8175f, + 1100.0635f, + 1094.759f, + 1041.7854f, + 1040.7617f, + 1087.556f, + 1082.5011f}; + + std::vector ref_vec; + ref_vec.assign(ref_array, ref_array + ref_dim); + + std::vector test_vec; + test_vec.assign(array, array + array_dim); + + BOOST_CHECK(check_cdd(test_vec, ref_vec, 3)); +} + +BOOST_FIXTURE_TEST_CASE(test_getNetReacts, Fixture) { + + error = ENR_getNetReacts(p_handle, &array, &array_dim); + BOOST_REQUIRE(error == 0); + + const int ref_dim = 4; + float ref_array[ref_dim] = {18806.59f, + 85424.438f, + 115174.05f, + 238972.66f}; + + std::vector ref_vec; + ref_vec.assign(ref_array, ref_array + ref_dim); + + std::vector test_vec; + test_vec.assign(array, array + array_dim); + + BOOST_CHECK(check_cdd(test_vec, ref_vec, 2)); +} + +BOOST_FIXTURE_TEST_CASE(test_getEnergyUsage, Fixture) { + + int linkIdx; + + error = ENR_getEnergyUsage(p_handle, 1, &linkIdx, &array, &array_dim); + BOOST_REQUIRE(error == 0); + + const int ref_dim = 6; + float ref_array[ref_dim] = {57.712959f, + 75.0f, + 880.41583f, + 96.254318f, + 96.707115f, + 0.0f}; + + std::vector ref_vec; + ref_vec.assign(ref_array, ref_array + ref_dim); + + std::vector test_vec; + test_vec.assign(array, array + array_dim); + + BOOST_CHECK(check_cdd(test_vec, ref_vec, 3)); +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/tools/epanet-output/CMakeLists.txt b/tools/epanet-output/CMakeLists.txt new file mode 100644 index 0000000..01002d7 --- /dev/null +++ b/tools/epanet-output/CMakeLists.txt @@ -0,0 +1,40 @@ +# +# CMakeLists.txt - CMake configuration file for epanet-output library +# +# Created: March 9, 2018 +# Author: Michael E. Tryby +# US EPA ORD/NRMRL +# + +cmake_minimum_required (VERSION 3.0) + + +# Sets for output directory for executables and libraries. +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) + + +set(CMAKE_C_VISIBILITY_PRESET hidden) +set(CMAKE_CXX_VISIBILITY_PRESET hidden) +set(CMAKE_POSITION_INDEPENDENT_CODE ON) +set(CMAKE_INCLUDE_CURRENT_DIR ON) + + +# configure file groups +set(EPANET_OUT_SOURCES src/epanet_output.c src/errormanager.c) + + +# the binary output file API +add_library(epanet-output SHARED ${EPANET_OUT_SOURCES}) +target_include_directories(epanet-output PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include) + +include(GenerateExportHeader) +generate_export_header(epanet-output + BASE_NAME epanet_output + EXPORT_MACRO_NAME DLLEXPORT + EXPORT_FILE_NAME epanet_output_export.h + STATIC_DEFINE SHARED_EXPORTS_BUILT_AS_STATIC) + +file(COPY ${CMAKE_CURRENT_BINARY_DIR}/epanet_output_export.h DESTINATION + ${CMAKE_CURRENT_SOURCE_DIR}/include) diff --git a/tools/epanet-output/src/epanet_output.h b/tools/epanet-output/include/epanet_output.h similarity index 93% rename from tools/epanet-output/src/epanet_output.h rename to tools/epanet-output/include/epanet_output.h index 36f826c..0066d16 100644 --- a/tools/epanet-output/src/epanet_output.h +++ b/tools/epanet-output/include/epanet_output.h @@ -61,15 +61,19 @@ typedef enum { } ENR_LinkAttribute; -#ifdef WINDOWS -#ifdef __cplusplus -#define DLLEXPORT __declspec(dllexport) __cdecl -#else -#define DLLEXPORT __declspec(dllexport) __stdcall -#endif -#else -#define DLLEXPORT -#endif +// #ifdef WINDOWS +// #ifdef __cplusplus +// #define DLLEXPORT __declspec(dllexport) __cdecl +// #else +// #define DLLEXPORT __declspec(dllexport) __stdcall +// #endif +// #else +// #define DLLEXPORT +// #endif + + +#include "epanet_output_export.h" + #ifdef __cplusplus extern "C" { diff --git a/tools/epanet-output/setup.py b/tools/epanet-output/setup.py index 90d57e0..d1ce65d 100644 --- a/tools/epanet-output/setup.py +++ b/tools/epanet-output/setup.py @@ -25,6 +25,8 @@ setup( version = "1.0", ext_modules = [ Extension("_epanet_output", + define_macros = [('epanet_output_EXPORTS', None)], + include_dirs = ['include'], sources = ['src/epanet_output.i', 'src/epanet_output.c', 'src/errormanager.c'], swig_opts=['-modern'], language = 'C' diff --git a/tools/requirements.txt b/tools/requirements.txt index 68b8c20..5a50cbd 100644 --- a/tools/requirements.txt +++ b/tools/requirements.txt @@ -11,6 +11,7 @@ # $ pip install --src build/packages -r tools/requirements.txt # --e git+https://github.com/OpenWaterAnalytics/nrtest.git@master#egg=nrtest +#-e git+https://github.com/OpenWaterAnalytics/nrtest.git@master#egg=nrtest +nrtest>=0.2.3 -e ./tools/epanet-output -e ./tools/nrtest-epanet