This document describes the coding conventions, project structure, test patterns, and build commands for the exhort-integration-tests repository.
- Primary language: Python 3.11+
- Testing framework: Custom Python test orchestration using subprocess calls
- CI/CD: GitHub Actions workflows
- Supported ecosystems: Maven, Gradle (Groovy & Kotlin), npm, pnpm, Yarn (Classic & Berry), pip, pyproject (pip/uv/poetry), Cargo, Go, Syft
- Follow PEP 8 conventions for Python code
- Use 4 spaces for indentation (no tabs)
- Use snake_case for function and variable names
- Use SCREAMING_SNAKE_CASE for constants
- Use descriptive variable names that convey purpose
- Use docstrings for module-level functions explaining their purpose and parameters
- Use
#!/bin/bashshebang for all shell scripts - Use
set -eto fail on errors - Use descriptive variable names in UPPERCASE for environment variables
- Use lowercase for local variables
- Include comments explaining non-obvious logic
- Python scripts: Use snake_case (e.g.,
run_tests.py,common_test_functions.py) - Shell scripts: Use kebab-case (e.g.,
setup-runtime.sh,print-runtime.sh) - Scenario directories: Use kebab-case matching ecosystem name (e.g.,
maven/simple,gradle-kotlin/simple,python-pip/simple) - Spec files: Always named
spec.yamlwithin each scenario directory
- Python functions: Use snake_case and descriptive names (e.g.,
validate_analysis,get_manifest_file) - Constants: Use SCREAMING_SNAKE_CASE (e.g.,
VALID_LICENSE_CATEGORIES,VULN_SUMMARY_FIELDS) - Test validation functions: Prefix with
validate_(e.g.,validate_licenses,validate_html_output)
exhort-integration-tests/
├── scenarios/ # Test scenarios organized by ecosystem (e.g. maven/, npm/, cargo/)
├── shared-scripts/ # Test orchestration scripts (Python + Bash)
└── .github/workflows/ # GitHub Actions CI workflows
Each scenario directory must contain:
- A manifest file appropriate for the ecosystem (e.g.,
pom.xml,package.json,go.mod) - A
spec.yamlfile defining expected test results
Every scenario must have a spec.yaml file with the following structure:
title: Simple Maven Scenario
description: Test a simple Maven project with a few dependencies.
expect_success: true
stack_analysis:
scanned:
direct: 5 # Number of direct dependencies (deterministic, must match manifest)
transitive: 40 # Number of transitive dependencies (deterministic, must match resolved tree)
transitive_windows: 42 # Optional: OS-specific override for Windows (if different from base)
providers:
rhtpa:
sources:
- osv-github
- redhat-csaf
licenses:
expected_providers:
- deps.dev
component_analysis:
scanned:
direct: 5 # Number of direct dependencies
transitive: 0 # Component analysis typically scans only direct deps
providers:
rhtpa:
sources:
- osv-github
licenses:
expected_providers:
- deps.dev
license_check:
expect_success: truetitle: Brief descriptive title of the scenariodescription: Longer description of what is being testedexpect_success: Boolean indicating if commands should succeed (true) or fail (false)stack_analysis: Expected results for stack analysis commandscanned.direct: Number of direct dependencies (must match manifest exactly)scanned.transitive: Number of transitive dependencies (must match resolved dependency tree)scanned.transitive_windows: (Optional) OS-specific override for Windowsscanned.transitive_linux: (Optional) OS-specific override for Linuxscanned.transitive_macos: (Optional) OS-specific override for macOSproviders.<provider_name>.sources: List of expected data sourceslicenses.expected_providers: List of expected license providers
component_analysis: Expected results for component analysis command (same structure as stack_analysis)license_check.expect_success: Boolean for license command expectations
The transitive count in spec.yaml must be deterministic and exact. To determine the correct value:
- Build the scenario project with its native tooling (e.g.,
mvn dependency:tree,npm install && npm ls) - Count the actual transitive dependencies in the resolved dependency tree
- Use that exact count in the spec — do not estimate or use placeholder values
- If dependency counts change (e.g., after updating a dependency), update the spec to match
Some ecosystems have platform-specific dependencies (e.g., Python's colorama on Windows). When the transitive count varies by OS, use OS-specific override fields:
transitive_windows: Override for Windows (platform.system() == "Windows")transitive_linux: Override for Linux (platform.system() == "Linux")transitive_macos: Override for macOS (platform.system() == "Darwin")
The test runner checks for an OS-specific override first (transitive_<os>), then falls back to the base transitive field. The base transitive field is still required — it serves as the default for any OS without an explicit override.
Example:
stack_analysis:
scanned:
direct: 2
transitive: 10 # Default for Linux/macOS
transitive_windows: 11 # Windows pulls in colorama as a platform-specific dependencyImportant: Only transitive counts support OS overrides. The direct count should NOT have OS-specific variants, as direct dependencies are declared in the manifest and do not vary by platform.
- Use meaningful exit codes (0 for success, 1 for failure)
- Print clear error messages to stdout with descriptive prefixes (
FAIL,PASS,WARN) - Use
try-exceptblocks for subprocess calls and JSON parsing - Validate all required data structures before processing (check for missing keys, type mismatches)
- Return boolean values from validation functions to indicate pass/fail
- Use
set -eto exit immediately on error - Check command availability before use (e.g.,
command -v) - Print descriptive messages for each installation step
- Handle OS-specific logic with case statements
Tests are organized as scenarios, each consisting of:
- A real manifest file for a specific ecosystem
- A
spec.yamldefining expected behavior - Commands executed by
run_tests.pythat invoke the Exhort CLI
Tests validate responses using structural and invariant checks:
- Structural checks: Verify required fields exist (
scanned,providers,licenses) - Deterministic checks: Verify counts match expected values exactly (direct dependencies, transitive dependencies)
- Invariant checks: Verify mathematical relationships hold:
total == direct + transitive(dependency counts)total == critical + high + medium + low + unknown(vulnerability counts)total == permissive + weakCopyleft + strongCopyleft + unknown(license categories)- All counts must be non-negative
- Provider checks: Verify expected providers are present and have
ok: truestatus withcode: 200 - Liveness checks: Warn if vulnerability totals are zero (may indicate data source issues)
To add a new test scenario for an existing or new ecosystem:
- Create a scenario directory:
scenarios/<ecosystem>/simple/(use kebab-case) - Add a minimal but representative manifest file:
- Include both direct and transitive dependencies
- Use real dependencies that are likely to have vulnerability data
- Keep the project minimal (4-5 direct dependencies is typical)
- Create a
spec.yamlfollowing the format above:- Determine exact dependency counts by building the project locally
- List expected providers and sources based on ecosystem support
- Set
expect_success: truefor valid scenarios
- If adding a new ecosystem runtime:
- Update
common_test_functions.py:- Add manifest filename to
get_manifest_file() - Add package manager to
get_package_manager() - Add scenario base directory to
get_scenario_base_dir()
- Add manifest filename to
- Update
setup-runtime.shto install the ecosystem tooling if needed - Add the runtime to the GitHub Actions matrix in
.github/workflows/integration.yml
- Update
Test validation functions print results with prefixes:
PASS— Check passedFAIL— Check failed (test should fail)WARN— Potential issue (does not fail test)
Example output:
---
Scenario: Simple Maven Scenario
Description: Test a simple Maven project with a few dependencies.
Manifest: /path/to/scenarios/maven/simple/pom.xml
Expect success: true
Executing: java -jar cli.jar component pom.xml
PASS Command succeeded as expected
Validating component analysis...
PASS component_analysis structural and invariant checks passed
PASS component_analysis license checks passed
---
Follow Conventional Commits specification:
- Format:
<type>[optional scope]: <description> - Types:
feat,fix,refactor,test,docs,chore,ci - Scope examples:
maven,npm,gradle,cargo,ci,scripts - Examples:
feat(npm): add yarn-berry simple scenariofix(scripts): correct transitive dependency count validationtest(go): update spec.yaml with correct dependency countsci: add windows runner to integration tests
The project uses minimal Python dependencies:
pyyaml— For reading spec.yaml files- No other external dependencies for core test scripts
Test scenarios may install ecosystem-specific dependencies as part of the test (e.g., requests, numpy, pandas for python-pip scenarios), but these are not project dependencies.
Each ecosystem requires its native tooling installed:
- Maven:
mvn(Java 17+) - Gradle:
gradle(Java 17+) - npm/pnpm:
npm,pnpm(Node.js 20+) - Yarn Classic:
yarnv1.x (Node.js 20+) - Yarn Berry:
yarnv4.x (Node.js 20+, using corepack) - Go:
go1.21+ or latest - Python pip:
pip(Python 3.10+) - Cargo:
cargo(Rust stable) - Syft:
syftCLI
Run all checks before committing:
# Verify Python syntax (no formal linter configured, but code should be valid)
python3 -m py_compile shared-scripts/*.py
# Run integration tests locally (requires CLI artifact and runtime)
python3 shared-scripts/run_tests.py <language> <cli_dir> <runtime>The integration workflow (.github/workflows/integration.yml) runs tests across:
- Operating systems: Ubuntu, Windows, macOS
- Runtimes: All supported ecosystems and versions
- Test phases:
- Build CLI artifact (Java or JavaScript)
- Setup runtime environment
- Run tests without runtime-specific setup (
run_tests_no_runtime.py) - Setup ecosystem runtimes (
setup-runtime.sh) - Run full integration tests (
run_tests.py)
To run tests locally:
- Build or obtain the Exhort CLI artifact (Java JAR or JavaScript tarball)
- Ensure the target ecosystem runtime is installed (e.g.,
mvn,npm,go) - Set environment variables:
export TRUSTIFY_DA_DEV_MODE=true export TRUSTIFY_DA_BACKEND_URL='https://exhort.stage.devshift.net'
- Run tests for a specific ecosystem:
python3 shared-scripts/run_tests.py java /path/to/cli maven python3 shared-scripts/run_tests.py javascript /path/to/cli npm
<language>: CLI language (javaorjavascript)<cli_dir>: Directory containingcli.jar(Java) orcli.tgz(JavaScript)<runtime>: Target ecosystem runtime (e.g.,maven,npm,go-latest,python-3.12-pip)
For each scenario, the test runner executes:
component <manifest>— Component analysis (direct dependencies only)stack <manifest>— Full stack analysis (direct + transitive)stack <manifest> --summary— Stack analysis summary outputstack <manifest> --html— HTML report generationlicense <manifest>— License mismatch detection
Each command's output is validated against the spec.yaml expectations.
- Keep test scenarios minimal but representative
- Ensure spec.yaml dependency counts are deterministic and accurate
- Validate all invariants (mathematical relationships between fields)
- Print clear failure messages that identify the exact field or check that failed
- Avoid hardcoded assumptions about vulnerability data (counts may change as databases update)
- Use structural validation (fields exist, types correct) over exact value matching for non-deterministic data