Skip to content

Commit 0c15754

Browse files
committed
Add WASM test runner.
Build tests.cpp as an Emscripten/Node runner so the wasm build can be checked with the same native test coverage. The wrapper sources emsdk when needed and decodes uncaught C++ exceptions into useful error messages. Wasm builds now support C++ exception handling, so the try/catch assertions should no longer be skipped in tests.cpp. Removing the guards keeps native and wasm test coverage aligned. Necessary other change: move Emscripten createNew wrappers into bindings. Keep the JavaScript-friendly TIVarFile.createNew(string, ...) API without adding Emscripten-only string overloads to the C++ class. Those overloads made tests.cpp ambiguous when compiled with __EMSCRIPTEN__, so the wasm tests can now build the same source without undefining the platform macro.
1 parent 5a5cf7a commit 0c15754

8 files changed

Lines changed: 134 additions & 29 deletions

File tree

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@ Thumbs.db
6060
*.data
6161
*.bc
6262
*.js.mem
63+
tivars_tests_wasm.js
64+
tivars_tests_wasm.wasm
6365

6466
# IDEs things
6567
**/.idea/

Makefile.emscripten

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,15 @@ SOURCES := $(wildcard src/*.cpp) $(wildcard src/TypeHandlers/*.cpp) $(wildcard s
2424
OBJS = $(patsubst %.c, %.bc, $(patsubst %.cpp, %.bc, $(SOURCES)))
2525

2626
OUTPUT := TIVarsLib
27+
TEST_OUTPUT := tivars_tests_wasm
2728

2829
wasm: $(OUTPUT).js
2930

3031
all: wasm
3132

33+
wasm-tests: $(TEST_OUTPUT).js
34+
node --no-warnings scripts/run_wasm_tests.mjs
35+
3236
%.bc: %.cpp
3337
$(CXX) $(CXXFLAGS) -c $< -o $@
3438

@@ -38,7 +42,16 @@ all: wasm
3842
$(OUTPUT).js: $(OBJS)
3943
$(CXX) $(CXXFLAGS) $(LFLAGS) $^ -o $@
4044

45+
tests_wasm.bc: tests.cpp
46+
$(CXX) $(CXXFLAGS) -Dmain=tivars_tests_main -c $< -o $@
47+
48+
scripts/wasm_tests_main.bc: scripts/wasm_tests_main.cpp
49+
$(CXX) $(CXXFLAGS) -c $< -o $@
50+
51+
$(TEST_OUTPUT).js: $(filter-out src/main_emscripten.bc,$(OBJS)) tests_wasm.bc scripts/wasm_tests_main.bc
52+
$(CXX) $(CXXFLAGS) $(OPTFLAGS) --bind -s WASM=1 -s MODULARIZE=1 -s EXPORT_ES6=1 -s EXPORT_NAME="'TIVarsTests'" -s NODERAWFS=1 -s EXIT_RUNTIME=1 -s ASSERTIONS=$(ASSERTIONS) -s DISABLE_EXCEPTION_CATCHING=0 -s ALLOW_MEMORY_GROWTH=1 -s EXPORTED_RUNTIME_METHODS="['callMain','getExceptionMessage','decrementExceptionRefcount']" $^ -o $@
53+
4154
clean:
42-
$(RM) -f $(OBJS) $(OUTPUT).js* $(OUTPUT).was*
55+
$(RM) -f $(OBJS) tests_wasm.bc scripts/wasm_tests_main.bc $(OUTPUT).js* $(OUTPUT).was* $(TEST_OUTPUT).js* $(TEST_OUTPUT).was*
4356

44-
.PHONY: all clean wasm
57+
.PHONY: all clean wasm wasm-tests

scripts/run_wasm_tests.mjs

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/*
2+
* Part of tivars_lib_cpp
3+
* (C) 2015-2026 Adrien "Adriweb" Bertrand
4+
* https://github.com/adriweb/tivars_lib_cpp
5+
* License: MIT
6+
*/
7+
8+
import { dirname, resolve } from "node:path";
9+
import { fileURLToPath, pathToFileURL } from "node:url";
10+
11+
const scriptDir = dirname(fileURLToPath(import.meta.url));
12+
const rootDir = resolve(scriptDir, "..");
13+
process.chdir(rootDir);
14+
15+
const { default: TIVarsTests } = await import(pathToFileURL(resolve(rootDir, "tivars_tests_wasm.js")).href);
16+
const module = await TIVarsTests({ noInitialRun: true });
17+
18+
try
19+
{
20+
module.callMain(process.argv.slice(2));
21+
}
22+
catch (e)
23+
{
24+
if (e && e.name === "ExitStatus")
25+
{
26+
process.exitCode = e.status;
27+
}
28+
else if (e && typeof e.excPtr === "number" && module.getExceptionMessage)
29+
{
30+
const [type, message] = module.getExceptionMessage(e);
31+
module.decrementExceptionRefcount(e);
32+
console.error(`Unhandled C++ exception in wasm tests: ${type}: ${message}`);
33+
process.exitCode = 1;
34+
}
35+
else
36+
{
37+
console.error(e);
38+
process.exitCode = 1;
39+
}
40+
}

scripts/wasm_tests.sh

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
#!/usr/bin/env bash
2+
set -euo pipefail
3+
4+
ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
5+
6+
if ! command -v em++ >/dev/null 2>&1; then
7+
if [[ -n "${EMSDK_ENV:-}" && -f "$EMSDK_ENV" ]]; then
8+
# shellcheck disable=SC1090
9+
source "$EMSDK_ENV" >/dev/null 2>&1
10+
elif [[ -f "$HOME/emsdk/emsdk_env.sh" ]]; then
11+
# shellcheck disable=SC1091
12+
source "$HOME/emsdk/emsdk_env.sh" >/dev/null 2>&1
13+
fi
14+
fi
15+
16+
if ! command -v em++ >/dev/null 2>&1; then
17+
echo "em++ not found; source emsdk_env.sh first or set EMSDK_ENV=/path/to/emsdk_env.sh" >&2
18+
exit 127
19+
fi
20+
21+
if ! command -v node >/dev/null 2>&1; then
22+
echo "node not found; Node.js is required to run the wasm tests" >&2
23+
exit 127
24+
fi
25+
26+
make -C "$ROOT_DIR" -f Makefile.emscripten wasm-tests

scripts/wasm_tests_main.cpp

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/*
2+
* Part of tivars_lib_cpp
3+
* (C) 2015-2026 Adrien "Adriweb" Bertrand
4+
* https://github.com/adriweb/tivars_lib_cpp
5+
* License: MIT
6+
*/
7+
8+
#include <exception>
9+
#include <iostream>
10+
11+
int tivars_tests_main(int argc, char** argv);
12+
13+
int main(int argc, char** argv)
14+
{
15+
try
16+
{
17+
return tivars_tests_main(argc, argv);
18+
}
19+
catch (const std::exception& e)
20+
{
21+
std::cerr << "Unhandled C++ exception in wasm tests: " << e.what() << '\n';
22+
return 1;
23+
}
24+
catch (...)
25+
{
26+
std::cerr << "Unhandled non-standard exception in wasm tests\n";
27+
return 1;
28+
}
29+
}

src/TIVarFile.cpp

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1047,6 +1047,25 @@ namespace tivars
10471047
#ifdef __EMSCRIPTEN__
10481048
#include <emscripten/bind.h>
10491049
using namespace emscripten;
1050+
1051+
namespace
1052+
{
1053+
tivars::TIVarFile createNewFromStrings(const std::string& type, const std::string& name, const std::string& model)
1054+
{
1055+
return tivars::TIVarFile::createNew(tivars::TIVarType{type}, name, tivars::TIModel{model});
1056+
}
1057+
1058+
tivars::TIVarFile createNewFromStrings(const std::string& type, const std::string& name)
1059+
{
1060+
return tivars::TIVarFile::createNew(tivars::TIVarType{type}, name);
1061+
}
1062+
1063+
tivars::TIVarFile createNewFromString(const std::string& type)
1064+
{
1065+
return tivars::TIVarFile::createNew(tivars::TIVarType{type});
1066+
}
1067+
}
1068+
10501069
EMSCRIPTEN_BINDINGS(_tivarfile) {
10511070

10521071
register_map<std::string, int>("options_t");
@@ -1076,9 +1095,9 @@ namespace tivars
10761095
.function("saveVarToFile" , select_overload<std::string(void)>(&tivars::TIVarFile::saveVarToFile))
10771096

10781097
.class_function("loadFromFile", &tivars::TIVarFile::loadFromFile, return_value_policy::take_ownership())
1079-
.class_function("createNew", select_overload<tivars::TIVarFile(const std::string&, const std::string&, const std::string&)>(&tivars::TIVarFile::createNew), return_value_policy::take_ownership())
1080-
.class_function("createNew", select_overload<tivars::TIVarFile(const std::string&, const std::string&)>(&tivars::TIVarFile::createNew), return_value_policy::take_ownership())
1081-
.class_function("createNew", select_overload<tivars::TIVarFile(const std::string&)>(&tivars::TIVarFile::createNew), return_value_policy::take_ownership())
1098+
.class_function("createNew", select_overload<tivars::TIVarFile(const std::string&, const std::string&, const std::string&)>(&createNewFromStrings), return_value_policy::take_ownership())
1099+
.class_function("createNew", select_overload<tivars::TIVarFile(const std::string&, const std::string&)>(&createNewFromStrings), return_value_policy::take_ownership())
1100+
.class_function("createNew", &createNewFromString, return_value_policy::take_ownership())
10821101
;
10831102
}
10841103
#endif

src/TIVarFile.h

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -72,22 +72,6 @@ namespace tivars
7272
static TIVarFile createNew(const TIVarType& type, const std::string& name);
7373
static TIVarFile createNew(const TIVarType& type);
7474

75-
// Additional overloads for easier Emscripten usage
76-
#ifdef __EMSCRIPTEN__
77-
static TIVarFile createNew(const std::string& type, const std::string& name, const std::string& model)
78-
{
79-
return TIVarFile{TIVarType{type}, name, TIModel{model}};
80-
}
81-
static TIVarFile createNew(const std::string& type, const std::string& name)
82-
{
83-
return createNew(TIVarType{type}, name);
84-
}
85-
static TIVarFile createNew(const std::string& type)
86-
{
87-
return createNew(TIVarType{type});
88-
}
89-
#endif
90-
9175
uint16_t getChecksumValueFromFile();
9276

9377
void setContentFromData(const data_t& data, uint16_t entryIdx);

tests.cpp

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -487,7 +487,6 @@ int main(int argc, char** argv)
487487
assert(TH_Matrix::getMinVersionFromData(makeCollectionData(0x1C)) == VER_CE_EXACTONLY);
488488
}
489489

490-
#ifndef __EMSCRIPTEN__
491490
{
492491
// TH_TempEqu error handling: too short and inconsistent lengths
493492
try {
@@ -500,7 +499,6 @@ int main(int argc, char** argv)
500499
assert(false);
501500
} catch (const std::invalid_argument&) {}
502501
}
503-
#endif
504502

505503
{
506504
// Alias tokenization: arcsin/asin variants should map to sin⁻¹(
@@ -1203,7 +1201,6 @@ int main(int argc, char** argv)
12031201
assert(testReal.getReadableContent() == "-1.2345678901235e30");
12041202
}
12051203

1206-
#ifndef __EMSCRIPTEN__
12071204
{
12081205
try
12091206
{
@@ -1216,7 +1213,6 @@ int main(int argc, char** argv)
12161213
cout << "Caught expected exception: " << e.what() << endl;
12171214
}
12181215
}
1219-
#endif
12201216

12211217
{
12221218
TIVarFile testReal42 = TIVarFile::createNew("Real", "R");
@@ -1343,7 +1339,6 @@ End)";
13431339
assert(trim(testPrgmReindent.getReadableContent({{"prettify", true}, {"reindent", true}})) == "\"http://TIPlanet.org");
13441340
}
13451341

1346-
#ifndef __EMSCRIPTEN__
13471342
{
13481343
try
13491344
{
@@ -1360,7 +1355,6 @@ End)";
13601355
cout << "Caught expected exception: " << e.what() << endl;
13611356
}
13621357
}
1363-
#endif
13641358

13651359
assert(TIVarType{"ExactRealPi"}.getId() == 32);
13661360

@@ -1466,7 +1460,6 @@ End)";
14661460
assert(matrixWithNewline.getReadableContent() == "[[1,2][3,4]]");
14671461
}
14681462

1469-
#ifndef __EMSCRIPTEN__
14701463
{
14711464
try
14721465
{
@@ -1496,7 +1489,6 @@ End)";
14961489
{
14971490
}
14981491
}
1499-
#endif
15001492

15011493
{
15021494
TIVarFile testComplex = TIVarFile::loadFromFile("testData/Complex.8xc"); // -5 + 2i

0 commit comments

Comments
 (0)