Skip to content

Commit d0cb469

Browse files
Add fixed_vector container and migrate tests to Google Test (#93)
* wip * wip * wip * changing orc_test to add tests it finds to gtest for consistent output and gtest capabilities * refactor: replace manual array implementation with fixed_vector in object_ancestry struct * tweak * readding a reserve call * readding a reserve call * updating workflow * adding new gtest to markdown script and wiring it up to the workflow * string parsing fix * workflow script tweak * workflow script tweak * refactor: update gtest_to_markdown.py to require context parameter and update workflow * removing unneeded scripts * adding docs * putting a comment back in * tweaking the tagged release script * Improve gtest to markdown conversion: Add duration parsing, enhance failure formatting, and improve output table
1 parent 1db995c commit d0cb469

12 files changed

Lines changed: 971 additions & 250 deletions

.github/generate_job_summary.py

Lines changed: 0 additions & 72 deletions
This file was deleted.

.github/gtest_to_markdown.py

Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
1+
#!/usr/bin/env python3
2+
3+
"""
4+
Google Test to Markdown Converter
5+
6+
This script converts Google Test (gtest) JSON output into GitHub-flavored markdown format.
7+
It processes test results and generates a well-formatted markdown document that includes:
8+
- A timestamped header
9+
- Summary statistics (total, passed, and failed tests)
10+
- A detailed table of test results
11+
- Failure details for any failed tests
12+
13+
The script is designed to be used as a command-line tool, taking two arguments:
14+
1. context: A string describing the test run context
15+
2. gtest_json_file: Path to the JSON file containing gtest results
16+
17+
Example usage:
18+
python gtest_to_markdown.py "Unit Tests" test_results.json
19+
20+
The output is formatted markdown that can be directly used in GitHub issues, pull requests,
21+
or documentation.
22+
"""
23+
24+
import json
25+
import os
26+
import sys
27+
from datetime import datetime
28+
from typing import Dict, List, Any
29+
30+
def format_duration(milliseconds: float) -> str:
31+
"""
32+
Convert milliseconds to a human-readable duration string.
33+
34+
Args:
35+
milliseconds (float): Duration in milliseconds
36+
37+
Returns:
38+
str: Human-readable duration string in the format:
39+
- "X.XXms" for durations < 1 second
40+
- "X.XXs" for durations < 1 minute
41+
- "X.XXm" for durations >= 1 minute
42+
43+
Example:
44+
>>> format_duration(500)
45+
'500.00ms'
46+
>>> format_duration(1500)
47+
'1.50s'
48+
"""
49+
if milliseconds < 1000:
50+
return f"{milliseconds:.2f}ms"
51+
seconds = milliseconds / 1000
52+
if seconds < 60:
53+
return f"{seconds:.2f}s"
54+
minutes = seconds / 60
55+
return f"{minutes:.2f}m"
56+
57+
def parse_duration(time_value: Any) -> float:
58+
"""
59+
Parse a duration value from gtest output into milliseconds.
60+
61+
Args:
62+
time_value (Any): The duration value from gtest, which could be:
63+
- A float (milliseconds)
64+
- A string ending in 's' (seconds)
65+
- Any other value that should be converted to float
66+
67+
Returns:
68+
float: Duration in milliseconds
69+
70+
Example:
71+
>>> parse_duration(500)
72+
500.0
73+
>>> parse_duration("1.5s")
74+
1500.0
75+
"""
76+
try:
77+
if isinstance(time_value, str) and time_value.endswith('s'):
78+
return float(time_value[:-1]) * 1000 # Convert seconds to milliseconds
79+
return float(time_value)
80+
except (ValueError, TypeError):
81+
return 0.0 # Return 0 for invalid values
82+
83+
def format_failure_message(failure: Dict[str, Any]) -> str:
84+
"""
85+
Format a test failure message from the gtest JSON output.
86+
87+
Args:
88+
failure (Dict[str, Any]): A dictionary containing failure information
89+
with optional keys:
90+
- failure: The failure message
91+
- type: The type of failure
92+
93+
Returns:
94+
str: The message with all newlines replaced with "<br/>"
95+
96+
Example:
97+
>>> failure = {"failure": "Expected 2\nbut got 3"}
98+
>>> format_failure_message(failure)
99+
'Expected 2<br/>but got 3'
100+
"""
101+
if "failure" in failure:
102+
return "<pre>" + failure["failure"].replace("\n", "<br/>") + "</pre>"
103+
return ""
104+
105+
def convert_to_markdown(data: Dict[str, Any], context: str) -> str:
106+
"""
107+
Convert gtest JSON data to GitHub-flavored markdown.
108+
109+
This function processes the gtest JSON output and generates a comprehensive
110+
markdown document that includes test results, statistics, and failure details.
111+
112+
Args:
113+
data (Dict[str, Any]): The parsed JSON data from gtest output
114+
context (str): A string describing the context of the test run
115+
(e.g., "Unit Tests", "Integration Tests")
116+
117+
Returns:
118+
str: A complete markdown document containing:
119+
- Header with timestamp
120+
- Summary statistics
121+
- Detailed test results table
122+
- Failure details for failed tests
123+
124+
The output markdown includes:
125+
- A table with columns: Test Suite, Test Case, Status, Duration
126+
- Emoji indicators (✅ for pass, ❌ for fail)
127+
- Formatted duration strings
128+
- Code blocks for failure messages
129+
"""
130+
output = []
131+
132+
# Add header with timestamp
133+
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
134+
output.append(f"# {context} Results ({timestamp})\n")
135+
136+
# Add summary
137+
total_tests = data.get("tests", 0)
138+
failed_tests = data.get("failures", 0)
139+
disabled_tests = data.get("disabled", 0)
140+
error_tests = data.get("errors", 0)
141+
tests_duration = format_duration(parse_duration(data.get("time", 0)))
142+
passed_tests = total_tests - failed_tests
143+
144+
output.append("## Summary\n")
145+
output.append(f"- Tests: {total_tests}")
146+
output.append(f"- Passed: {passed_tests}")
147+
output.append(f"- Failed: {failed_tests}")
148+
output.append(f"- Disabled: {disabled_tests}")
149+
output.append(f"- Errors: {error_tests}")
150+
output.append(f"- Duration: {tests_duration}\n")
151+
152+
# Add detailed results table
153+
output.append("## Details\n")
154+
output.append("| Suite | Case | Status | Duration | Details |")
155+
output.append("|-------|------|--------|----------|---------|")
156+
157+
for suite in data.get("testsuites", []):
158+
suite_name = suite.get("name", "Unknown Suite")
159+
for test in suite.get("testsuite", []):
160+
test_name = test.get("name", "Unknown Test")
161+
status = "❌ FAIL" if "failures" in test else "✅ PASS"
162+
duration = format_duration(parse_duration(test.get("time", 0)))
163+
details = []
164+
165+
# Add failure details if the test failed
166+
if "failures" in test:
167+
for failure in test["failures"]:
168+
details.append(format_failure_message(failure))
169+
170+
# Add the test result row
171+
output.append(f"| {suite_name} | {test_name} | {status} | {duration} | {'<br/>'.join(details)}")
172+
173+
return "\n".join(output)
174+
175+
def main():
176+
"""
177+
Main entry point for the script.
178+
179+
Processes command line arguments and converts gtest JSON output to markdown.
180+
The script expects two arguments:
181+
1. context: A string describing the test run context
182+
2. gtest_json_file: Path to the JSON file containing gtest results
183+
184+
The script will:
185+
- Read and parse the JSON file
186+
- Convert the data to markdown format
187+
- Print the markdown to stdout
188+
189+
Exits with status code 1 if:
190+
- Incorrect number of arguments
191+
- File not found
192+
- Invalid JSON
193+
- Any other error occurs
194+
"""
195+
if len(sys.argv) != 3:
196+
print("Usage: python gtest_to_markdown.py <context> <gtest_json_file>")
197+
sys.exit(1)
198+
199+
context = sys.argv[1]
200+
json_file = sys.argv[2]
201+
202+
try:
203+
with open(json_file, 'r') as f:
204+
data = json.load(f)
205+
markdown = convert_to_markdown(data, context)
206+
print(markdown)
207+
except FileNotFoundError:
208+
print(f"Error: File {json_file} not found", file=sys.stderr)
209+
sys.exit(1)
210+
except json.JSONDecodeError:
211+
print(f"Error: Invalid JSON in {json_file}", file=sys.stderr)
212+
sys.exit(1)
213+
except Exception as e:
214+
print(f"Error: {str(e)}", file=sys.stderr)
215+
sys.exit(1)
216+
217+
if __name__ == "__main__":
218+
main()

.github/orc_test_to_github_actions.py

Lines changed: 0 additions & 6 deletions
This file was deleted.

.github/workflows/build-and-test.yml

Lines changed: 8 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -71,8 +71,8 @@ jobs:
7171
id: run-orc_test
7272
continue-on-error: true
7373
run: |
74-
./build/Release/orc_test ./test/battery --json_mode > test_out.json
75-
python ${GITHUB_WORKSPACE}/.github/orc_test_to_github_actions.py test_out.json
74+
./build/Release/orc_test ./test/battery --gtest_output="json:test_out.json"
75+
python ${GITHUB_WORKSPACE}/.github/gtest_to_markdown.py "Release Tests" test_out.json >> $GITHUB_STEP_SUMMARY
7676
- name: 🛠️ orc_test w/ ASan
7777
id: build-orc_test-asan
7878
continue-on-error: true
@@ -82,8 +82,8 @@ jobs:
8282
id: run-orc_test-asan
8383
continue-on-error: true
8484
run: |
85-
./build/Release/orc_test ./test/battery --json_mode > test_out.json
86-
python ${GITHUB_WORKSPACE}/.github/orc_test_to_github_actions.py test_out.json
85+
./build/Release/orc_test ./test/battery --gtest_output="json:test_out.json"
86+
python ${GITHUB_WORKSPACE}/.github/gtest_to_markdown.py "Address Sanitizer Tests" test_out.json >> $GITHUB_STEP_SUMMARY
8787
- name: 🛠️ orc_test w/ TSan
8888
id: build-orc_test-tsan
8989
continue-on-error: true
@@ -93,8 +93,8 @@ jobs:
9393
id: run-orc_test-tsan
9494
continue-on-error: true
9595
run: |
96-
./build/Release/orc_test ./test/battery --json_mode > test_out.json
97-
python ${GITHUB_WORKSPACE}/.github/orc_test_to_github_actions.py test_out.json
96+
./build/Release/orc_test ./test/battery --gtest_output="json:test_out.json"
97+
python ${GITHUB_WORKSPACE}/.github/gtest_to_markdown.py "Thread Sanitizer Tests" test_out.json >> $GITHUB_STEP_SUMMARY
9898
- name: 🛠️ orc_test w/ UBSan
9999
id: build-orc_test-ubsan
100100
continue-on-error: true
@@ -104,21 +104,5 @@ jobs:
104104
id: run-orc_test-ubsan
105105
continue-on-error: true
106106
run: |
107-
./build/Release/orc_test ./test/battery --json_mode > test_out.json
108-
python ${GITHUB_WORKSPACE}/.github/orc_test_to_github_actions.py test_out.json
109-
- name: ✏️ github json
110-
uses: jsdaniell/create-json@1.1.2
111-
continue-on-error: true
112-
with:
113-
name: "github.json"
114-
json: ${{ toJSON(github) }}
115-
- name: ✏️ steps json
116-
uses: jsdaniell/create-json@1.1.2
117-
continue-on-error: true
118-
with:
119-
name: "steps.json"
120-
json: ${{ toJSON(steps) }}
121-
- name: ✍️ job summary
122-
continue-on-error: false
123-
run: |
124-
python ${GITHUB_WORKSPACE}/.github/generate_job_summary.py $GITHUB_STEP_SUMMARY github.json steps.json
107+
./build/Release/orc_test ./test/battery --gtest_output="json:test_out.json"
108+
python ${GITHUB_WORKSPACE}/.github/gtest_to_markdown.py "Undefined Behavior Sanitizer Tests" test_out.json >> $GITHUB_STEP_SUMMARY

.github/workflows/tagged-release.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@ jobs:
1515
python-version: 3.8
1616
- name: ⬇️ Checkout sources
1717
uses: actions/checkout@v3
18+
with:
19+
lfs: true
20+
- name: 🏗️ Checkout LFS objects
21+
run: git lfs pull
1822
- name: 🏗️ Setup project files
1923
run: |
2024
mkdir build

CMakeLists.txt

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,23 @@ if (NOT TARGET nlohmann_json::nlohmann_json)
6363
FetchContent_MakeAvailable(json)
6464
endif()
6565

66+
####################################################################################################
67+
#
68+
# Adds support for Google Test.
69+
#
70+
71+
if (NOT TARGET GTest::gtest)
72+
message(STATUS "ORC third-party: creating target 'GTest::gtest'...")
73+
FetchContent_Declare(
74+
googletest
75+
GIT_REPOSITORY https://github.com/google/googletest.git
76+
GIT_TAG v1.14.0
77+
)
78+
# Prevent overriding the parent project's compiler/linker settings
79+
set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)
80+
FetchContent_MakeAvailable(googletest)
81+
endif()
82+
6683
####################################################################################################
6784
#
6885
# Adds support for the Tracy profiler.
@@ -185,6 +202,7 @@ target_link_libraries(orc_test
185202
tomlplusplus::tomlplusplus
186203
Tracy::TracyClient
187204
nlohmann_json::nlohmann_json
205+
GTest::gtest
188206
)
189207
if (PROJECT_IS_TOP_LEVEL)
190208
target_compile_options(orc_test PRIVATE -Wall -Werror)

0 commit comments

Comments
 (0)