Skip to content

Commit 7452c87

Browse files
edwardbrclaude
andcommitted
Add GTest support and GitHub Actions CI
- Convert tests to Google Test so TestMate can discover them - Add CANOPY_BUILD_GTEST option so consumers can get googletest without pulling in all of Canopy's own test suite - GitHub Actions workflow: checkout CanopyJSON + Canopy as siblings, Canopy's cmake handles submodule init automatically Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent bd234c7 commit 7452c87

6 files changed

Lines changed: 218 additions & 67 deletions

File tree

.github/workflows/main.yml

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches: [main]
6+
pull_request:
7+
branches: [main]
8+
9+
jobs:
10+
test:
11+
runs-on: ubuntu-24.04
12+
13+
steps:
14+
- name: Checkout CanopyJSON
15+
uses: actions/checkout@v4
16+
with:
17+
path: CanopyJSON
18+
19+
# Canopy must sit at ../Canopy relative to CanopyJSON so that
20+
# add_subdirectory(../Canopy canopy_build) in CMakeLists.txt resolves correctly.
21+
# Canopy's cmake/Canopy.cmake initialises only the submodules it needs at
22+
# configure time, so no manual submodule steps are required here.
23+
# If Canopy is a private repository, add a personal access token as the
24+
# secret CANOPY_PAT (Settings → Secrets and variables → Actions) and
25+
# uncomment the token line below.
26+
- name: Checkout Canopy
27+
uses: actions/checkout@v4
28+
with:
29+
repository: edwardbr/Canopy
30+
path: Canopy
31+
# token: ${{ secrets.CANOPY_PAT }}
32+
33+
- name: Install dependencies
34+
run: |
35+
sudo apt-get update -qq
36+
sudo apt-get install -y clang ninja-build libssl-dev
37+
38+
- name: Configure
39+
working-directory: CanopyJSON
40+
run: cmake --preset Debug
41+
42+
- name: Build
43+
working-directory: CanopyJSON
44+
run: cmake --build build_debug --target json_test --parallel $(nproc)
45+
46+
- name: Test
47+
working-directory: CanopyJSON
48+
run: ctest --test-dir build_debug --output-on-failure

.vscode/extensions.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"recommendations": [
3+
"cheshirekow.cmake-format",
4+
"ms-vscode.cmake-tools"
5+
]
6+
}

.vscode/settings.json

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
{
2+
"cmake.cmakePath": "/usr/local/bin/cmake",
3+
"files.associations": {
4+
"chrono": "cpp",
5+
"map": "cpp",
6+
"vector": "cpp",
7+
"algorithm": "cpp",
8+
"filesystem": "cpp",
9+
"tuple": "cpp",
10+
"array": "cpp",
11+
"complex": "cpp",
12+
"limits": "cpp",
13+
"cerrno": "cpp",
14+
"atomic": "cpp",
15+
"unordered_map": "cpp",
16+
"thread": "cpp",
17+
"string_view": "cpp",
18+
"iostream": "cpp",
19+
"type_traits": "cpp",
20+
"shared_mutex": "cpp",
21+
"memory": "cpp",
22+
"format": "cpp",
23+
"future": "cpp",
24+
"istream": "cpp",
25+
"functional": "cpp",
26+
"utility": "cpp",
27+
"variant": "cpp",
28+
"condition_variable": "cpp",
29+
"memory_resource": "cpp",
30+
"mutex": "cpp",
31+
"any": "cpp",
32+
"bit": "cpp",
33+
"bitset": "cpp",
34+
"cctype": "cpp",
35+
"cfenv": "cpp",
36+
"charconv": "cpp",
37+
"cinttypes": "cpp",
38+
"clocale": "cpp",
39+
"cmath": "cpp",
40+
"codecvt": "cpp",
41+
"compare": "cpp",
42+
"concepts": "cpp",
43+
"coroutine": "cpp",
44+
"csetjmp": "cpp",
45+
"csignal": "cpp",
46+
"cstdarg": "cpp",
47+
"cstddef": "cpp",
48+
"cstdint": "cpp",
49+
"cstdio": "cpp",
50+
"cstdlib": "cpp",
51+
"cstring": "cpp",
52+
"ctime": "cpp",
53+
"cuchar": "cpp",
54+
"cwchar": "cpp",
55+
"cwctype": "cpp",
56+
"deque": "cpp",
57+
"forward_list": "cpp",
58+
"list": "cpp",
59+
"set": "cpp",
60+
"string": "cpp",
61+
"unordered_set": "cpp",
62+
"exception": "cpp",
63+
"expected": "cpp",
64+
"iterator": "cpp",
65+
"numeric": "cpp",
66+
"optional": "cpp",
67+
"random": "cpp",
68+
"ratio": "cpp",
69+
"regex": "cpp",
70+
"source_location": "cpp",
71+
"system_error": "cpp",
72+
"fstream": "cpp",
73+
"initializer_list": "cpp",
74+
"iomanip": "cpp",
75+
"iosfwd": "cpp",
76+
"new": "cpp",
77+
"numbers": "cpp",
78+
"ostream": "cpp",
79+
"queue": "cpp",
80+
"ranges": "cpp",
81+
"scoped_allocator": "cpp",
82+
"semaphore": "cpp",
83+
"span": "cpp",
84+
"sstream": "cpp",
85+
"stack": "cpp",
86+
"stdexcept": "cpp",
87+
"stop_token": "cpp",
88+
"streambuf": "cpp",
89+
"text_encoding": "cpp",
90+
"typeindex": "cpp",
91+
"typeinfo": "cpp",
92+
"valarray": "cpp"
93+
},
94+
"testMate.cpp.test.advancedExecutables": [
95+
{
96+
"pattern": "build*/output/*",
97+
"type": "gtest"
98+
}
99+
],
100+
"editor.formatOnSave": true,
101+
"[cmake]": {
102+
"editor.defaultFormatter": "cheshirekow.cmake-format"
103+
},
104+
"cmake-format.args": [
105+
"--config-files",
106+
"${workspaceFolder}/.cmake-format.yaml"
107+
],
108+
"protobuf.clangFormat.enabled": true,
109+
"protobuf.clangFormat.path": "/usr/bin/clang-format",
110+
"git.detectSubmodulesLimit": 50
111+
}

CMakeLists.txt

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,25 @@ set(CANOPY_JSON_SOURCE_DIR
1515
"${CMAKE_CURRENT_SOURCE_DIR}"
1616
CACHE INTERNAL "Root of the CanopyJson source tree")
1717

18+
option(CANOPY_JSON_BUILD_TEST "Build CanopyJson tests" OFF)
19+
1820
if("${CMAKE_CURRENT_SOURCE_DIR}" PATH_EQUAL "${CMAKE_SOURCE_DIR}")
1921
# Standalone build: pull in Canopy and configure output directories.
22+
# CANOPY_BUILD_TEST must be ON when our tests are enabled so that Canopy
23+
# builds googletest (GTest::gtest_main) which our test targets depend on.
2024
set(CANOPY_BUILD_TEST
2125
OFF
2226
CACHE BOOL "" FORCE)
27+
# CANOPY_BUILD_GTEST enables googletest without pulling in all of Canopy's own tests.
28+
if(CANOPY_JSON_BUILD_TEST)
29+
set(CANOPY_BUILD_GTEST
30+
ON
31+
CACHE BOOL "" FORCE)
32+
else()
33+
set(CANOPY_BUILD_GTEST
34+
OFF
35+
CACHE BOOL "" FORCE)
36+
endif()
2337
set(CANOPY_BUILD_DEMOS
2438
OFF
2539
CACHE BOOL "" FORCE)
@@ -39,7 +53,6 @@ endif()
3953

4054
add_subdirectory(interface)
4155

42-
option(CANOPY_JSON_BUILD_TEST "Build CanopyJson tests" OFF)
4356
if(CANOPY_JSON_BUILD_TEST)
4457
add_subdirectory(c++/tests)
4558
endif()

c++/tests/CMakeLists.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,13 @@ target_link_libraries(
2424
json_idl
2525
rpc::rpc
2626
yas_common
27+
GTest::gtest_main
2728
${CANOPY_LIBRARIES})
2829

2930
target_compile_options(json_test PRIVATE ${CANOPY_COMPILE_OPTIONS} ${CANOPY_WARN_OK})
3031
target_link_options(json_test PRIVATE ${CANOPY_LINK_EXE_OPTIONS})
3132

32-
add_test(NAME json_test COMMAND json_test)
33-
# Skip gtest_discover_tests when using AddressSanitizer due to protobuf container - overflow false positives
33+
# Skip gtest_discover_tests when using AddressSanitizer due to protobuf container-overflow false positives
3434
if(NOT CANOPY_DEBUG_ADDRESS)
3535
include(GoogleTest)
3636
gtest_discover_tests(json_test)

c++/tests/src/main.cpp

Lines changed: 37 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,87 +1,60 @@
11
// Copyright (c) 2026 Edward Boggis-Rolfe All rights reserved.
22

3-
#include <cstdio>
4-
#include <cstring>
3+
#include <gtest/gtest.h>
54

65
#include "json_test_impl.h"
76

8-
// ---------------------------------------------------------------------------
9-
// Minimal test runner — no external test framework dependency.
10-
// Returns 0 if all tests pass, 1 on the first failure.
11-
// ---------------------------------------------------------------------------
12-
13-
int main()
7+
namespace json_test
148
{
15-
json_test::json_test_impl impl;
16-
int result;
17-
18-
// --- test_json_objects --------------------------------------------------
199

20-
result = impl.test_json_objects();
21-
if (result != 0)
10+
TEST(JsonObjects, AllChecks)
2211
{
23-
std::fprintf(stderr, "FAILED test_json_objects (returned %d)\n", result);
24-
return 1;
12+
json_test_impl impl;
13+
ASSERT_EQ(0, impl.test_json_objects());
2514
}
26-
std::printf("PASSED test_json_objects\n");
27-
28-
// --- test_json_serialisation --------------------------------------------
2915

16+
TEST(JsonSerialisation, NullRoundtrip)
3017
{
31-
// Null roundtrip
18+
json_test_impl impl;
3219
json::v1::object in(nullptr);
3320
json::v1::object out;
34-
result = impl.test_json_serialisation(in, out);
35-
if (result != 0 || out != in)
36-
{
37-
std::fprintf(stderr, "FAILED test_json_serialisation/null (returned %d)\n", result);
38-
return 1;
39-
}
21+
ASSERT_EQ(0, impl.test_json_serialisation(in, out));
22+
ASSERT_EQ(in, out);
23+
}
4024

41-
// String roundtrip
42-
in = json::v1::object(std::string("hello"));
43-
out = json::v1::object(nullptr);
44-
result = impl.test_json_serialisation(in, out);
45-
if (result != 0 || out != in)
46-
{
47-
std::fprintf(stderr, "FAILED test_json_serialisation/string (returned %d)\n", result);
48-
return 1;
49-
}
25+
TEST(JsonSerialisation, StringRoundtrip)
26+
{
27+
json_test_impl impl;
28+
json::v1::object in(std::string("hello"));
29+
json::v1::object out;
30+
ASSERT_EQ(0, impl.test_json_serialisation(in, out));
31+
ASSERT_EQ(in, out);
32+
}
5033

51-
// Number roundtrip
52-
in = json::v1::object(42L);
53-
out = json::v1::object(nullptr);
54-
result = impl.test_json_serialisation(in, out);
55-
if (result != 0 || out != in)
56-
{
57-
std::fprintf(stderr, "FAILED test_json_serialisation/number (returned %d)\n", result);
58-
return 1;
59-
}
34+
TEST(JsonSerialisation, NumberRoundtrip)
35+
{
36+
json_test_impl impl;
37+
json::v1::object in(42L);
38+
json::v1::object out;
39+
ASSERT_EQ(0, impl.test_json_serialisation(in, out));
40+
ASSERT_EQ(in, out);
41+
}
6042

61-
// Map roundtrip
43+
TEST(JsonSerialisation, MapRoundtrip)
44+
{
45+
json_test_impl impl;
6246
json::v1::map m;
6347
m.emplace("key", json::v1::object("value"));
64-
in = json::v1::object(m);
65-
out = json::v1::object(nullptr);
66-
result = impl.test_json_serialisation(in, out);
67-
if (result != 0 || out != in)
68-
{
69-
std::fprintf(stderr, "FAILED test_json_serialisation/map (returned %d)\n", result);
70-
return 1;
71-
}
48+
json::v1::object in(m);
49+
json::v1::object out;
50+
ASSERT_EQ(0, impl.test_json_serialisation(in, out));
51+
ASSERT_EQ(in, out);
7252
}
73-
std::printf("PASSED test_json_serialisation\n");
74-
75-
// --- simple_database_test -----------------------------------------------
7653

77-
result = impl.simple_database_test("test_app");
78-
if (result != 0)
54+
TEST(JsonDatabase, SimpleDatabaseTest)
7955
{
80-
std::fprintf(stderr, "FAILED simple_database_test (returned %d)\n", result);
81-
return 1;
56+
json_test_impl impl;
57+
ASSERT_EQ(0, impl.simple_database_test("test_app"));
8258
}
83-
std::printf("PASSED simple_database_test\n");
8459

85-
std::printf("\nAll tests passed.\n");
86-
return 0;
87-
}
60+
} // namespace json_test

0 commit comments

Comments
 (0)