Skip to content

Commit fdb003d

Browse files
maltesanderclaude
andcommitted
test: move the suites under integration-tests/ and factor out the shared script paths
`test/` and `windows/` were two top-level directories for one concern, and `test/` mixed four different things: the shell entry points, the SQL fixture, the pyodbc suite, and three generated files sitting in the same directory as the sources. The layout now matches stackable-odbc-trino's, minus everything that only exists because Trino needs a server: integration-tests/ setup.sh, run-tests.sh wrappers scripts/ lib.sh + the logic suites/ create_test_db.sql, test_integration.py generated/ gitignored wholesale windows/ WINDOWS.md, windows_test.py, vm/ There is no stack/, no compose file and no scripts/teardown.sh: SQLite is a file and rusqlite links its own copy, so there is nothing to stand up or tear down. That is also why this suite gates every pull request while Trino's cannot. The script cleanup: - Both scripts derived PROJECT_DIR, DRIVER_PATH and DB_PATH separately, and disagreed about which directory DB_PATH lived in relative to the script. They now source scripts/lib.sh, which owns those and the ODBCSYSINI/ODBCINI export, the driver build, and a setup precondition check. - Generated output moves out of the source directory into generated/, ignored by a `*` .gitignore rather than by naming each file. All of it embeds absolute paths, so a committed copy is wrong for everyone but its author. - run-tests.sh forwarded any unrecognised argument to windows_test.py even without --windows, so a typo'd flag produced a full green run that had ignored it. Unknown arguments are now rejected unless --windows is given. - run-tests.sh required setup.sh to have been run and said so nowhere; it now checks and points at setup.sh instead of failing inside pyodbc. - Both take --help, printed from the header comment block by lib.sh's usage() rather than a hardcoded line range that truncates as soon as a line is added. - setup.sh gained --skip-build, and rejects unknown arguments. shellcheck now runs with -x so it follows lib.sh, the same reason the Trino repository passes it; without it every sourcing script reports SC1091 for a file that is right there. Also fixes two path references that were already stale before the move: windows/vm/start.yaml pointed at `test/sqlite/windows_test.py`, a path this repository has never had, and test_integration.py's usage line named itself under test/. Verified by running setup.sh and run-tests.sh: 23 pyodbc tests pass DSN-less and again through the DSN, the require_setup guard fires with the database removed, and unknown arguments exit 2. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent 088a8a0 commit fdb003d

30 files changed

Lines changed: 354 additions & 154 deletions

.github/workflows/build.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -101,9 +101,9 @@ jobs:
101101

102102
- name: Run SQLite integration tests
103103
run: |
104-
./test/setup.sh
104+
./integration-tests/setup.sh
105105
# --skip-cargo-test: the pre-commit job above already ran it.
106-
./test/run-tests.sh --skip-cargo-test
106+
./integration-tests/run-tests.sh --skip-cargo-test
107107
108108
windows-cross-compile:
109109
name: Cross-compile Windows DLL

.github/workflows/release.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,8 +68,8 @@ jobs:
6868

6969
- name: Run SQLite integration tests
7070
run: |
71-
./test/setup.sh
72-
./test/run-tests.sh
71+
./integration-tests/setup.sh
72+
./integration-tests/run-tests.sh
7373
7474
build-and-package:
7575
name: Build and package SQLite release archives

.gitignore

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@ tags
1717
# Release packaging output
1818
packaging/dist/
1919

20-
# Generated by test/setup.sh
21-
test/test.db
20+
# integration-tests/generated/ has its own .gitignore; everything setup.sh
21+
# writes there embeds absolute paths.
2222

2323
# Python bytecode from the test scripts
2424
__pycache__/

.pre-commit-config.yaml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,10 @@ repos:
2525
rev: 2491238703a5d3415bb2b7ff11388bf775372f29 # 0.10.0
2626
hooks:
2727
- id: shellcheck
28-
args: ["--severity=info"]
28+
# -x follows `source`d files. The integration-test scripts share
29+
# lib.sh, and without it every one of them reports SC1091 for a file
30+
# that is right there and checkable.
31+
args: ["--severity=info", "-x"]
2932

3033
- repo: local
3134
hooks:

AGENTS.md

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,8 @@ cargo test # unit + FFI tests; needs no server
3333
cargo clippy --all-targets -- -D warnings
3434
pre-commit run --all-files # the gate; run before every commit
3535

36-
./test/setup.sh # build driver, create test.db, write ODBC config
37-
./test/run-tests.sh # run the integration suite
36+
./integration-tests/setup.sh # build driver, create the DB, write ODBC config
37+
./integration-tests/run-tests.sh # run the integration suite
3838
```
3939

4040
## Relationship to stackable-odbc-core
@@ -534,21 +534,24 @@ with no data source open.
534534
### Integration tests
535535

536536
```bash
537-
./test/setup.sh # build, create test/test.db, write odbc.ini/odbcinst.ini
538-
./test/run-tests.sh # pyodbc suite through real unixODBC, then cargo test
539-
./test/run-tests.sh --windows # also run the Windows VM suite
540-
./test/run-tests.sh --skip-cargo-test # pyodbc only; what CI passes
537+
./integration-tests/setup.sh # build, create the database, write the ODBC config
538+
./integration-tests/run-tests.sh # pyodbc suite through real unixODBC, then cargo test
539+
./integration-tests/run-tests.sh --windows # also run the Windows VM suite
540+
./integration-tests/run-tests.sh --skip-cargo-test # pyodbc only; what CI passes
541541
```
542542

543-
`test/setup.sh` and `test/run-tests.sh` regenerate `test/odbc.ini`,
544-
`test/odbcinst.ini` and `test/test.db`; all three are gitignored because they
545-
hold absolute paths.
543+
Both are wrappers; the logic is in `integration-tests/scripts/`, with the paths
544+
and helpers they share in `scripts/lib.sh`. Everything `setup.sh` writes lands
545+
in `integration-tests/generated/`, which is gitignored wholesale because all of
546+
it embeds absolute paths. See
547+
[integration-tests/README.md](integration-tests/README.md) for the layout and
548+
why the pyodbc suite is run twice.
546549

547550
### Windows VM tests
548551

549-
See [windows/WINDOWS.md](windows/WINDOWS.md). Requires a provisioned libvirt VM;
550-
`test/windows_test.py` runs the same pyodbc suite over WinRM, DSN-less and then
551-
via DSN.
552+
See [integration-tests/windows/WINDOWS.md](integration-tests/windows/WINDOWS.md).
553+
Requires a provisioned libvirt VM; `integration-tests/windows/windows_test.py`
554+
runs the same pyodbc suite over WinRM, DSN-less and then via DSN.
552555

553556
### Benchmarks
554557

README.md

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -226,13 +226,15 @@ The integration suite goes one layer further out and runs through real
226226
unixODBC, using Python's `pyodbc` exactly like a normal application would:
227227

228228
```bash
229-
./test/setup.sh # build the driver, create test/test.db, write the ODBC config
230-
./test/run-tests.sh # run the pyodbc suite, then cargo test
229+
./integration-tests/setup.sh # build the driver, create the database, write the ODBC config
230+
./integration-tests/run-tests.sh # run the pyodbc suite, then cargo test
231231
```
232232

233-
Both are run on every pull request. `./test/run-tests.sh --windows` additionally
234-
runs the same suite inside a Windows VM; see
235-
[windows/WINDOWS.md](windows/WINDOWS.md) for how to provision one.
233+
Both are run on every pull request. `run-tests.sh --windows` additionally runs
234+
the same suite inside a Windows VM; see
235+
[integration-tests/README.md](integration-tests/README.md) for what is covered
236+
and [integration-tests/windows/WINDOWS.md](integration-tests/windows/WINDOWS.md)
237+
for how to provision one.
236238

237239
For the architecture, the conventions and the full testing reference, see
238240
[AGENTS.md](AGENTS.md).

integration-tests/README.md

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
# Integration tests
2+
3+
Everything that exercises the driver from outside the Rust crate: through real
4+
unixODBC on Linux, and through the real Windows Driver Manager in a VM.
5+
6+
There is no service to start. SQLite is a file, and `rusqlite` links its own
7+
copy of it into the driver, so the whole suite runs on a bare runner in seconds.
8+
That is exactly why it gates every pull request while the Trino driver's
9+
equivalent cannot.
10+
11+
```bash
12+
./integration-tests/setup.sh # build the driver, create the database, write the ODBC config
13+
./integration-tests/run-tests.sh # pyodbc through unixODBC, then cargo test
14+
```
15+
16+
Both take `--help`.
17+
18+
## Layout
19+
20+
| Path | What it holds |
21+
|------|---------------|
22+
| `setup.sh`, `run-tests.sh` | Wrappers. The logic is in `scripts/` |
23+
| `scripts/lib.sh` | The paths and helpers both scripts share. Sourced, never executed |
24+
| `scripts/setup.sh` | Builds the driver, creates `test.db`, writes `odbc.ini` / `odbcinst.ini` |
25+
| `scripts/run-tests.sh` | Runs the suites |
26+
| `suites/create_test_db.sql` | The schema and rows every suite reads |
27+
| `suites/test_integration.py` | The pyodbc suite, run once per connection style |
28+
| `generated/` | Everything `setup.sh` writes. Gitignored |
29+
| `windows/` | The VM suite, its libvirt definitions, and [WINDOWS.md](windows/WINDOWS.md) |
30+
31+
`generated/` is ignored rather than committed because all three files it holds
32+
name absolute paths: the driver's `.so`, the database. None of them survives
33+
being moved to another checkout, so a committed copy would be wrong for
34+
everyone but its author.
35+
36+
## What gets run
37+
38+
`run-tests.sh` runs the pyodbc suite **twice**, against the same database:
39+
40+
- **DSN-less**, `Driver=...;Database=...`, which exercises this driver's own
41+
connection-string parsing.
42+
- **Via a DSN**, `DSN=test_sqlite`, where the Driver Manager resolves the
43+
keywords out of `odbc.ini` first.
44+
45+
They are separate runs because they fail separately. A driver that reads its
46+
parameters correctly can still be unreachable through a DSN, and that is a
47+
configuration most applications actually use.
48+
49+
It then runs `cargo test`, so that one command gives a developer the whole
50+
suite. CI passes `--skip-cargo-test`, since its pre-commit job has already run
51+
exactly that via the `cargo-test` hook.
52+
53+
## Options
54+
55+
| Flag | Effect |
56+
|------|--------|
57+
| `--skip-build` | Reuse the driver already built. Forwarded to `windows_test.py`, whose build is a separate cross-compile |
58+
| `--skip-cargo-test` | Run the pyodbc suites only. What CI passes |
59+
| `--windows` | Additionally run the suite inside the Windows VM |
60+
61+
Any other argument is forwarded to `windows_test.py` (`--host`, `--gateway`,
62+
`--user`, `--password`) and so is rejected without `--windows`: a flag
63+
forwarded to a script that never runs is a flag silently ignored.
64+
65+
## Windows
66+
67+
`windows/windows_test.py` deploys the cross-compiled DLL to a provisioned
68+
libvirt VM over WinRM, registers it, and runs the same
69+
`suites/test_integration.py` through the Windows Driver Manager, DSN-less and
70+
then via a DSN. The Windows DM is much stricter than unixODBC and tends to fail
71+
silently, so this is measured rather than assumed.
72+
73+
See [windows/WINDOWS.md](windows/WINDOWS.md) for provisioning the VM.
74+
75+
## Interactively
76+
77+
`setup.sh` prints these at the end:
78+
79+
```bash
80+
export ODBCSYSINI=integration-tests/generated
81+
export ODBCINI=integration-tests/generated/odbc.ini
82+
isql -3 test_sqlite -v
83+
```
84+
85+
Prefix either with `ODBC_LOG_LEVEL=debug` to see which ODBC functions your
86+
client calls, and in what order.
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# Everything setup.sh writes here embeds absolute paths, so none of it is
2+
# portable between checkouts. Keep the directory, ignore the contents.
3+
*
4+
!.gitignore

integration-tests/run-tests.sh

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
#!/usr/bin/env bash
2+
# Wrapper. The logic lives in scripts/run-tests.sh.
3+
exec "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/scripts/run-tests.sh" "$@"

integration-tests/scripts/lib.sh

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
#!/usr/bin/env bash
2+
# Shared paths and helpers. Sourced, never executed.
3+
#
4+
# SC2034: every variable below is consumed by a script that sources this file,
5+
# which shellcheck cannot see from here.
6+
# shellcheck disable=SC2034
7+
8+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
9+
TEST_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
10+
PROJECT_DIR="$(cd "$TEST_DIR/.." && pwd)"
11+
12+
SUITES_DIR="$TEST_DIR/suites"
13+
WINDOWS_DIR="$TEST_DIR/windows"
14+
15+
# Everything setup.sh writes lands here, and the whole directory is gitignored:
16+
# all three files embed absolute paths, so none of them is portable between
17+
# checkouts.
18+
GENERATED="$TEST_DIR/generated"
19+
DB_PATH="$GENERATED/test.db"
20+
ODBC_INI="$GENERATED/odbc.ini"
21+
ODBCINST_INI="$GENERATED/odbcinst.ini"
22+
23+
DRIVER_PATH="$PROJECT_DIR/target/debug/libstackable_odbc_sqlite.so"
24+
25+
# The DSN setup.sh writes into odbc.ini, and the one run-tests.sh connects
26+
# through for its second configuration.
27+
DSN_NAME="test_sqlite"
28+
29+
mkdir -p "$GENERATED"
30+
31+
# Point unixODBC at the generated configuration rather than the system's.
32+
# ODBCSYSINI is a *directory* (unixODBC appends `odbcinst.ini` itself) while
33+
# ODBCINI is a full path, which is why the two are not spelled alike.
34+
use_generated_odbc_config() {
35+
export ODBCSYSINI="$GENERATED"
36+
export ODBCINI="$ODBC_INI"
37+
}
38+
39+
# Build the cdylib pyodbc loads. `cargo test` builds the test harness, not this,
40+
# so a run that skips it would silently exercise the previous build. cargo is
41+
# incremental, so repeating it costs nothing when nothing changed.
42+
build_driver() {
43+
echo "=== Building stackable-odbc-sqlite ==="
44+
(cd "$PROJECT_DIR" && cargo build)
45+
}
46+
47+
# usage <script>. Prints the contiguous comment block below the shebang, minus
48+
# the leading `# `, as that script's help text. Derived rather than given as a
49+
# line range, which silently truncates the moment a line is added to the header.
50+
usage() {
51+
awk 'NR > 1 && /^#/ { sub(/^# ?/, ""); print; next } NR > 1 { exit }' "$1"
52+
}
53+
54+
require_setup() {
55+
if [[ ! -f "$DB_PATH" || ! -f "$ODBC_INI" ]]; then
56+
echo "ERROR: $GENERATED is incomplete. Run ./integration-tests/setup.sh first." >&2
57+
exit 1
58+
fi
59+
}

0 commit comments

Comments
 (0)