Skip to content

Commit 9858ed8

Browse files
committed
test: add regression test framework with basic-crud scenario
Parameterized gtest runner auto-discovers fixtures from 3-expected/*/, builds batch-mode OLR configs, and compares JSON output against golden files. generate.sh validates each fixture against Oracle LogMiner before saving. CI workflows: generate-fixtures (weekly) + run-tests (push/PR).
1 parent cd7a0a1 commit 9858ed8

11 files changed

Lines changed: 1993 additions & 0 deletions

File tree

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
name: Generate Test Fixtures
2+
3+
on:
4+
workflow_dispatch:
5+
schedule:
6+
- cron: '0 6 * * 1' # weekly Monday 06:00 UTC
7+
8+
jobs:
9+
generate:
10+
runs-on: ubuntu-latest
11+
timeout-minutes: 45
12+
steps:
13+
- uses: actions/checkout@v4
14+
15+
- uses: docker/setup-buildx-action@v3
16+
17+
- name: Build OLR image (cached)
18+
uses: docker/build-push-action@v6
19+
with:
20+
context: .
21+
file: Dockerfile.dev
22+
load: true
23+
tags: olr-dev:latest
24+
cache-from: type=gha
25+
cache-to: type=gha,mode=max
26+
build-args: |
27+
BUILD_TYPE=Debug
28+
UIDOLR=1001
29+
GIDOLR=1001
30+
WITHORACLE=1
31+
WITHKAFKA=1
32+
WITHPROTOBUF=1
33+
WITHPROMETHEUS=1
34+
WITHTESTS=1
35+
36+
- name: Start containers
37+
run: make up
38+
39+
- name: Generate all fixtures
40+
run: make testdata
41+
42+
- name: Upload test fixtures
43+
uses: actions/upload-artifact@v4
44+
with:
45+
name: test-fixtures
46+
path: |
47+
tests/1-schema/
48+
tests/2-redo/
49+
tests/3-expected/
50+
retention-days: 90

.github/workflows/run-tests.yaml

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
name: Run Tests
2+
3+
on:
4+
push:
5+
branches: [master]
6+
pull_request:
7+
branches: [master]
8+
9+
jobs:
10+
test:
11+
runs-on: ubuntu-latest
12+
timeout-minutes: 15
13+
steps:
14+
- uses: actions/checkout@v4
15+
16+
- uses: docker/setup-buildx-action@v3
17+
18+
- name: Build OLR image (cached)
19+
uses: docker/build-push-action@v6
20+
with:
21+
context: .
22+
file: Dockerfile.dev
23+
load: true
24+
tags: olr-dev:latest
25+
cache-from: type=gha
26+
cache-to: type=gha,mode=max
27+
build-args: |
28+
BUILD_TYPE=Debug
29+
UIDOLR=1001
30+
GIDOLR=1001
31+
WITHORACLE=1
32+
WITHKAFKA=1
33+
WITHPROTOBUF=1
34+
WITHPROMETHEUS=1
35+
WITHTESTS=1
36+
37+
- name: Download test fixtures
38+
uses: dawidd6/action-download-artifact@v6
39+
with:
40+
workflow: generate-fixtures.yaml
41+
name: test-fixtures
42+
path: tests/
43+
44+
- name: Run tests
45+
run: |
46+
make up
47+
make test

CMakeLists.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,10 @@ if (WITH_PROTOBUF)
127127
endif ()
128128

129129
add_subdirectory(src)
130+
if (WITH_TESTS)
131+
enable_testing()
132+
add_subdirectory(tests)
133+
endif ()
130134

131135
target_link_libraries(OpenLogReplicator Threads::Threads)
132136

tests/0-inputs/basic-crud.sql

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
-- basic-crud.sql: Simple INSERT/UPDATE/DELETE scenario for fixture generation.
2+
-- Run as PDB user (e.g., olr_test/olr_test@//localhost:1521/FREEPDB1)
3+
--
4+
-- Outputs: FIXTURE_SCN_START: <scn> and FIXTURE_SCN_END: <scn>
5+
-- The orchestrator script parses these and handles log switches separately
6+
-- (ALTER SYSTEM SWITCH LOGFILE must run from CDB root, not PDB).
7+
8+
SET SERVEROUTPUT ON
9+
SET FEEDBACK OFF
10+
SET ECHO OFF
11+
12+
-- Setup: drop and recreate table
13+
DECLARE
14+
v_table_exists NUMBER;
15+
BEGIN
16+
SELECT COUNT(*) INTO v_table_exists
17+
FROM user_tables WHERE table_name = 'TEST_CDC';
18+
IF v_table_exists > 0 THEN
19+
EXECUTE IMMEDIATE 'DROP TABLE TEST_CDC PURGE';
20+
END IF;
21+
END;
22+
/
23+
24+
CREATE TABLE TEST_CDC (
25+
id NUMBER PRIMARY KEY,
26+
name VARCHAR2(100),
27+
val NUMBER
28+
);
29+
30+
ALTER TABLE TEST_CDC ADD SUPPLEMENTAL LOG DATA (ALL) COLUMNS;
31+
32+
-- Record start SCN
33+
DECLARE
34+
v_start_scn NUMBER;
35+
BEGIN
36+
SELECT current_scn INTO v_start_scn FROM v$database;
37+
DBMS_OUTPUT.PUT_LINE('FIXTURE_SCN_START: ' || v_start_scn);
38+
END;
39+
/
40+
41+
-- DML: INSERTs
42+
INSERT INTO TEST_CDC VALUES (1, 'Alice', 100);
43+
INSERT INTO TEST_CDC VALUES (2, 'Bob', 200);
44+
INSERT INTO TEST_CDC VALUES (3, 'Charlie', 300);
45+
COMMIT;
46+
47+
-- DML: UPDATE
48+
UPDATE TEST_CDC SET val = 150, name = 'Alice Updated' WHERE id = 1;
49+
COMMIT;
50+
51+
-- DML: DELETE
52+
DELETE FROM TEST_CDC WHERE id = 2;
53+
COMMIT;
54+
55+
-- Record end SCN
56+
DECLARE
57+
v_end_scn NUMBER;
58+
BEGIN
59+
SELECT current_scn INTO v_end_scn FROM v$database;
60+
DBMS_OUTPUT.PUT_LINE('FIXTURE_SCN_END: ' || v_end_scn);
61+
END;
62+
/
63+
64+
EXIT

tests/CMakeLists.txt

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
include(FetchContent)
2+
FetchContent_Declare(
3+
googletest
4+
URL https://github.com/google/googletest/releases/download/v1.15.2/googletest-1.15.2.tar.gz
5+
URL_HASH SHA256=7b42b4d6ed48810c5362c265a17faebe90dc2373c885e5216439d37927f02926
6+
DOWNLOAD_EXTRACT_TIMESTAMP TRUE
7+
)
8+
set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)
9+
set(INSTALL_GTEST OFF CACHE BOOL "" FORCE)
10+
FetchContent_MakeAvailable(googletest)
11+
12+
add_executable(olr_tests
13+
test_pipeline.cpp
14+
)
15+
16+
target_compile_definitions(olr_tests PRIVATE
17+
OLR_BINARY_PATH="$<TARGET_FILE:OpenLogReplicator>"
18+
OLR_TEST_DATA_DIR="${CMAKE_CURRENT_SOURCE_DIR}"
19+
)
20+
21+
target_link_libraries(olr_tests PRIVATE GTest::gtest_main)
22+
add_dependencies(olr_tests OpenLogReplicator)
23+
24+
include(GoogleTest)
25+
gtest_discover_tests(olr_tests DISCOVERY_MODE PRE_TEST)

tests/README.md

Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
# OpenLogReplicator Test Framework
2+
3+
Automated regression testing for OpenLogReplicator. Each test runs OLR in batch
4+
mode against captured Oracle redo logs and compares JSON output against golden
5+
files validated by Oracle LogMiner.
6+
7+
## Prerequisites
8+
9+
- Docker with Compose v2
10+
- Python 3.6+ (stdlib only, no pip dependencies)
11+
- Bash
12+
13+
No C++ toolchain needed on the host — OLR is built inside Docker.
14+
15+
## Quick Start
16+
17+
```bash
18+
# Build OLR Docker image (includes binary + gtest)
19+
make build
20+
21+
# Start OLR dev container + Oracle (~30s with slim-faststart image)
22+
make up
23+
24+
# Generate all fixtures (validates each against LogMiner)
25+
make testdata
26+
27+
# Run regression tests
28+
make test
29+
30+
# Generate just one fixture
31+
make testdata SCENARIO=basic-crud
32+
33+
# Cleanup
34+
make down
35+
```
36+
37+
## How It Works
38+
39+
### Build (`make build`)
40+
41+
Builds OLR inside a Docker image (`olr-dev`) using `Dockerfile.dev` at the
42+
project root via `docker compose build olr`. The image includes all optional
43+
dependencies (Oracle client, Kafka, Protobuf, Prometheus) with ccache for
44+
fast incremental rebuilds. Google Test is auto-fetched via CMake FetchContent.
45+
46+
### Fixture Generation (`make testdata`)
47+
48+
The `scripts/generate.sh` script runs 7 stages per scenario:
49+
50+
| Stage | Action |
51+
|-------|--------|
52+
| 0 | (DDL only) Build LogMiner dictionary into redo logs |
53+
| 1 | Run SQL scenario against Oracle, capture start/end SCN |
54+
| 2 | Force log switches, copy archived redo logs out of container |
55+
| 3 | Generate schema checkpoint via `gencfg.sql` |
56+
| 4 | Run LogMiner, convert output to JSON |
57+
| 5 | Run OLR in batch mode via `docker run` against captured redo logs |
58+
| 6 | Compare OLR output vs LogMiner — **fail if mismatch** |
59+
| 7 | Save OLR output as golden file |
60+
61+
### Regression Tests (`make test`)
62+
63+
Runs `ctest` inside the `olr-dev` Docker image with fixture directories mounted.
64+
The C++ test runner (`test_pipeline.cpp`) auto-discovers fixtures from
65+
`3-expected/*/output.json`, builds a batch-mode config for each, runs OLR,
66+
and compares output line-by-line against the golden file.
67+
68+
No Oracle instance is needed to run tests — only the pre-generated fixtures.
69+
70+
## Directory Structure
71+
72+
```
73+
Makefile # Build, run, test targets
74+
Dockerfile.dev # Builds OLR + gtest (full dev image)
75+
docker-compose.yaml # olr (dev container) + oracle (test DB)
76+
scripts/run.sh # Docker entrypoint script
77+
tests/
78+
CMakeLists.txt # gtest build config
79+
test_pipeline.cpp # Parameterized gtest runner
80+
scripts/
81+
generate.sh # Generate + validate one fixture
82+
compare.py # OLR vs LogMiner comparison
83+
logminer2json.py # LogMiner spool → JSON converter
84+
oracle-init/
85+
01-setup.sh # Enables archivelog + supplemental logging
86+
0-inputs/ # SQL scenarios (committed)
87+
basic-crud.sql
88+
data-types.sql
89+
...
90+
1-schema/ # Schema checkpoints (gitignored)
91+
2-redo/ # Redo log files (gitignored)
92+
3-expected/ # Golden files (gitignored)
93+
```
94+
95+
Only `0-inputs/`, `scripts/`, and build files are committed to git. The `1-schema/`, `2-redo/`, and `3-expected/` directories are generated
96+
and distributed as CI artifacts.
97+
98+
## Writing New Scenarios
99+
100+
Create a SQL file in `0-inputs/` that:
101+
102+
1. Creates test table(s) with supplemental logging
103+
2. Records start SCN via `DBMS_OUTPUT.PUT_LINE('FIXTURE_SCN_START: ' || scn)`
104+
3. Performs DML operations with explicit COMMITs
105+
4. Records end SCN via `DBMS_OUTPUT.PUT_LINE('FIXTURE_SCN_END: ' || scn)`
106+
5. Ends with `EXIT`
107+
108+
See `0-inputs/basic-crud.sql` for the template.
109+
110+
**Note:** Log switches are handled by `generate.sh` — don't run
111+
`ALTER SYSTEM SWITCH LOGFILE` from the scenario SQL.
112+
113+
### DDL Scenarios
114+
115+
For scenarios with DDL (ALTER TABLE, etc.), add `-- @DDL` at the top.
116+
This switches LogMiner to `DICT_FROM_REDO_LOGS` + `DDL_DICT_TRACKING`
117+
so it can track schema changes inline.
118+
119+
See `0-inputs/ddl-add-column.sql` for an example.
120+
121+
### Long-Spanning Transactions
122+
123+
For transactions that should span multiple archive logs, add `-- @MID_SWITCH`
124+
markers in the SQL where log switches should occur. The SQL should use
125+
`DBMS_SESSION.SLEEP()` at those points to allow time for the switch.
126+
127+
See `0-inputs/long-spanning-txn.sql` for an example.
128+
129+
## Comparison Details
130+
131+
The comparison tool (`scripts/compare.py`) handles:
132+
133+
- **Content-based matching**: pairs records by operation type, table, and column
134+
values rather than strict ordering (LogMiner orders by redo SCN, OLR by
135+
commit SCN)
136+
- **Type tolerance**: `"100"` matches `100`, float precision differences allowed
137+
- **Date/timestamp conversion**: Oracle format strings vs epoch seconds
138+
- **LOB merging**: Oracle splits LOB writes into INSERT(EMPTY_CLOB) + UPDATE;
139+
these are merged to match OLR's coalesced output
140+
- **Supplemental log columns**: OLR includes all columns via supplemental
141+
logging; extra columns beyond what LogMiner shows are allowed
142+
143+
## CI Workflows
144+
145+
### `generate-fixtures.yaml`
146+
147+
Runs weekly (or manually via `workflow_dispatch`). Starts Oracle, generates all
148+
fixtures with LogMiner validation, uploads as artifact (90-day retention).
149+
150+
### `run-tests.yaml`
151+
152+
Runs on push/PR to master. Downloads the latest fixture artifact and runs
153+
`ctest` inside the `olr-dev` Docker image. **Fails hard** if no artifact exists —
154+
run `generate-fixtures` first.
155+
156+
## Makefile Targets
157+
158+
| Target | Description |
159+
|--------|-------------|
160+
| `build` | Build OLR Docker image with tests |
161+
| `up` | Start OLR dev container + Oracle container |
162+
| `down` | Stop all containers |
163+
| `test` | Run regression tests via `ctest` (inside Docker) |
164+
| `testdata` | Generate all fixtures (or one with `SCENARIO=name`) |
165+
| `clean` | Remove generated fixture data |
166+
167+
## Environment Variables
168+
169+
| Variable | Default | Description |
170+
|----------|---------|-------------|
171+
| `ORACLE_CONTAINER` | `oracle` | Docker container name for Oracle |
172+
| `ORACLE_PASSWORD` | `oracle` | SYS/SYSTEM password |
173+
| `DB_CONN` | `olr_test/olr_test@//localhost:1521/FREEPDB1` | Test user connect string |
174+
| `SCHEMA_OWNER` | `OLR_TEST` | Schema owner for LogMiner filter |
175+
| `PDB_NAME` | `FREEPDB1` | PDB name for schema generation |
176+
177+
## Troubleshooting
178+
179+
If fixture generation fails at comparison, the working directory is preserved:
180+
181+
```bash
182+
# LogMiner parsed output
183+
cat tests/.work/basic-crud_XXXXXX/logminer.json
184+
185+
# OLR raw output
186+
cat tests/.work/basic-crud_XXXXXX/olr_output.json
187+
188+
# OLR log (includes redo parsing details)
189+
cat tests/.work/basic-crud_XXXXXX/olr_stdout.log
190+
191+
# Generated OLR config
192+
cat tests/.work/basic-crud_XXXXXX/olr_config.json
193+
```

0 commit comments

Comments
 (0)