Skip to content

Commit f350048

Browse files
authored
fix: Adding coverage report for integration tests (#599)
* fix: Adding coverage for integration tests * fix: Optimize integration test coverage * fix: Optimize fix * fix: Docker image fix * fix: Fix codecov config file * fix: Improvements to codecov config * fix: Fix codecov * fix: Commented out upload coverage tests * fix: Commit for re running tests * chore: Improvements * chore: Improvements
1 parent 1d24fb5 commit f350048

File tree

7 files changed

+129
-21
lines changed

7 files changed

+129
-21
lines changed

.github/codecov.yml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,20 @@ coverage:
1616
- ai
1717
- dev
1818
- properties
19+
# TODO: Integration tests will be included in the future when test cases are considerable enough
20+
# integration:
21+
# informational: true
22+
# target: auto
23+
# flags:
24+
# - integration
1925
patch:
2026
default:
2127
target: auto
2228
threshold: 1%
29+
flags:
30+
- ai
31+
- dev
32+
- properties
2333
flags:
2434
ai:
2535
paths:
@@ -29,6 +39,15 @@ flags:
2939
paths:
3040
- src/
3141
carryforward: true
42+
properties:
43+
paths:
44+
- src/
45+
carryforward: true
46+
# TODO: Integration flag will be re-enabled when test cases are considerable enough to include
47+
# integration:
48+
# paths:
49+
# - src/
50+
# carryforward: false # Don't carry forward manual test coverage
3251
comment:
3352
layout: flags, diff, files
3453
require_changes: false

.github/workflows/integration-tests.yaml

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ jobs:
3232
API_KEY=${{ secrets.INTEGRATION_TESTS_API_KEY }}
3333
LOG_LEVEL=info
3434
USE_KMS=true
35+
CI=true
3536
AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID}
3637
AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY}
3738
AWS_SESSION_TOKEN=${AWS_SESSION_TOKEN}
@@ -45,5 +46,31 @@ jobs:
4546
mkdir -p tests/integration/config/testnet
4647
echo '${{ vars.INTEGRATION_CONFIG_JSON }}' | envsubst > tests/integration/config/testnet/config.json
4748
echo '${{ vars.INTEGRATION_REGISTRY_JSON }}' > tests/integration/config/testnet/registry.json
49+
- name: Build Integration Test Image
50+
run: |
51+
docker build -f Dockerfile.integration --target runtime -t openzeppelin-relayer-integration:latest .
52+
- name: Create coverage directories
53+
run: mkdir -p ./coverage ./profraw
4854
- name: Run Integration Tests
4955
run: ./scripts/run-integration-docker.sh
56+
- name: Verify coverage exists
57+
if: always()
58+
run: |
59+
ls -lah ./coverage/
60+
if [ -f ./coverage/integration-lcov.info ]; then
61+
echo "✓ Coverage file found ($(wc -l < ./coverage/integration-lcov.info) lines)"
62+
else
63+
echo "✗ Coverage file not found!"
64+
exit 1
65+
fi
66+
# TODO: Integration test coverage will be included in the future when test cases are considerable enough to include
67+
# - name: Upload Integration Coverage to Codecov
68+
# if: always() # Upload coverage even if tests fail
69+
# uses: codecov/codecov-action@5a1091511ad55cbe89839c7260b706298ca349f7 # v5.5.1
70+
# with:
71+
# token: ${{ secrets.CODECOV_TOKEN }}
72+
# name: integration-coverage
73+
# files: ./coverage/integration-lcov.info
74+
# flags: integration
75+
# fail_ci_if_error: false
76+
# verbose: true

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,3 +66,5 @@ node_modules
6666
# Integration test artifacts
6767
test-results/
6868
**/*lcov.info
69+
profraw/
70+
coverage/

Dockerfile.integration

Lines changed: 42 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,11 @@ RUN apk update && apk --no-cache add \
1616

1717
WORKDIR /app
1818

19+
# Install llvm-tools-preview component and cargo-llvm-cov for coverage
20+
RUN rustup default stable && \
21+
rustup component add llvm-tools-preview && \
22+
cargo install cargo-llvm-cov --locked
23+
1924
# Copy manifests
2025
COPY Cargo.toml Cargo.lock ./
2126

@@ -28,27 +33,40 @@ RUN --mount=type=cache,target=/usr/local/cargo/registry \
2833
COPY src ./src
2934
COPY tests ./tests
3035

31-
# Build integration tests
32-
# Clear cached integration test artifacts to force recompilation when source changes.
33-
# This ensures changes to test files are always reflected in the binary while still
34-
# keeping dependency compilation cached for fast builds.
36+
# Build integration tests with coverage instrumentation
37+
# Using RUSTFLAGS to enable coverage instrumentation during compilation
38+
# This avoids the triple compilation issue (normal build, test build, coverage build)
3539
RUN --mount=type=cache,target=/usr/local/cargo/registry \
3640
--mount=type=cache,target=/usr/local/cargo/git \
3741
--mount=type=cache,target=/app/target \
38-
rm -rf /app/target/release/deps/integration-* \
39-
/app/target/release/.fingerprint/*integration* && \
42+
RUSTFLAGS="-C instrument-coverage" \
4043
cargo test --features integration-tests --test integration --release --no-run && \
4144
TEST_BINARY=$(find target/release/deps -name 'integration-*' -type f -executable | head -n 1) && \
4245
cp "$TEST_BINARY" /app/integration-test-binary
4346

4447
# ============================================================================
45-
# Runtime Stage - Minimal image with only what's needed to run tests
48+
# Runtime Stage - Minimal image with only what's needed to run tests and generate coverage
4649
# ============================================================================
47-
FROM cgr.dev/chainguard/wolfi-base
50+
FROM cgr.dev/chainguard/wolfi-base AS runtime
4851

4952
WORKDIR /app
5053

51-
# Copy test binary from builder
54+
# Copy LLVM tools and libraries from builder (architecture-agnostic with wildcards)
55+
# This fixes the hardcoded aarch64 issue - works on both x86_64 and aarch64
56+
COPY --from=builder /root/.rustup/toolchains/stable-*/lib/rustlib/*/bin/llvm-cov \
57+
/usr/local/bin/llvm-cov
58+
COPY --from=builder /root/.rustup/toolchains/stable-*/lib/rustlib/*/bin/llvm-profdata \
59+
/usr/local/bin/llvm-profdata
60+
61+
# Copy LLVM shared libraries that llvm-cov and llvm-profdata depend on
62+
# Use a broad pattern to cover libLLVM.so.* and libLLVM-*.so variants.
63+
COPY --from=builder /root/.rustup/toolchains/stable-*/lib/libLLVM*.so* /usr/local/lib/
64+
COPY --from=builder /root/.rustup/toolchains/stable-*/lib/librustc_driver-*.so /usr/local/lib/
65+
66+
# Update library path so the LLVM tools can find their dependencies
67+
ENV LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH
68+
69+
# Copy instrumented test binary from builder
5270
COPY --from=builder /app/integration-test-binary ./integration-tests
5371

5472
# Copy SSL libraries from builder
@@ -62,9 +80,19 @@ COPY tests/integration ./tests/integration
6280
# Networks is mounted separately at /app/networks to avoid nested mount issues
6381
RUN mkdir -p /app/config /app/networks
6482

65-
# Create directory for test results
66-
RUN mkdir -p /app/test-results
83+
# Create directories for coverage output with proper permissions
84+
RUN mkdir -p /app/coverage /app/profraw && chmod -R 777 /app/coverage /app/profraw
6785

68-
# Default test command
69-
# Can be overridden via docker-compose or docker run
70-
CMD ["./integration-tests", "--nocapture", "--test-threads=1"]
86+
# Execute tests directly, then generate lcov report using llvm-cov
87+
# This approach:
88+
# 1. Runs the instrumented test binary (generates .profraw files)
89+
# 2. Merges .profraw files into .profdata
90+
# 3. Generates lcov report from .profdata
91+
# All without invoking cargo or recompiling
92+
CMD ["sh", "-c", "\
93+
LLVM_PROFILE_FILE=/app/profraw/integration-%p-%m.profraw \
94+
./integration-tests --nocapture --test-threads=1 && \
95+
llvm-profdata merge -sparse /app/profraw/*.profraw -o /app/coverage/integration.profdata && \
96+
llvm-cov export --format=lcov \
97+
--instr-profile=/app/coverage/integration.profdata \
98+
./integration-tests > /app/coverage/integration-lcov.info"]

docker-compose.integration.yml

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -80,10 +80,13 @@ services:
8080
retries: 15
8181
start_period: 10s
8282
integration-tests:
83+
image: openzeppelin-relayer-integration:latest
8384
build:
8485
context: .
8586
dockerfile: Dockerfile.integration
87+
target: runtime # Use runtime stage that executes pre-built binary with coverage
8688
container_name: integration-tests
89+
user: ${HOST_UID:-1000}:${HOST_GID:-1000} # Run as host user for proper file permissions
8790
depends_on:
8891
relayer:
8992
condition: service_healthy
@@ -101,10 +104,10 @@ services:
101104
volumes:
102105
- ./tests/integration/config:/app/config:ro
103106
- ./config/networks:/app/networks:ro
104-
# Optional: mount test results output
105-
- ./test-results:/app/test-results
106-
# Uses the pre-built binary from Dockerfile.integration
107-
# The binary runs with --ignored --nocapture --test-threads=1 by default
107+
# Mount coverage output directory
108+
- ./coverage:/app/coverage
109+
# Mount profraw directory for debugging raw coverage data
110+
- ./profraw:/app/profraw
108111
networks:
109112
integration:
110113
driver: bridge

scripts/run-integration-docker.sh

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,13 @@ set -a
5151
source "$PROJECT_ROOT/.env.integration"
5252
set +a
5353

54+
# Export UID/GID for docker-compose (allows container to run as host user)
55+
export HOST_UID=$(id -u)
56+
export HOST_GID=$(id -g)
57+
58+
# Detect if running in CI
59+
CI=${CI:-false}
60+
5461
# MODE handling (local/testnet)
5562
MODE=${MODE:-local} # Default to local
5663

@@ -215,11 +222,33 @@ case "$COMMAND" in
215222
log_info "Starting relayer..."
216223
docker compose $PROFILE -f docker-compose.integration.yml up -d relayer
217224

218-
# Run tests
225+
# Create coverage directories (container runs as host user, so default perms are fine)
226+
mkdir -p "$PROJECT_ROOT/coverage" "$PROJECT_ROOT/profraw"
227+
228+
# Run tests - different behavior for CI vs local
219229
log_info "Running integration tests..."
220-
docker compose $PROFILE -f docker-compose.integration.yml up --build --abort-on-container-exit integration-tests
230+
if [ "$CI" = "true" ]; then
231+
# CI: Use pre-built images (already built by workflow)
232+
docker compose $PROFILE -f docker-compose.integration.yml up \
233+
--no-build \
234+
--abort-on-container-exit \
235+
integration-tests
236+
else
237+
# Local: Build if needed
238+
docker compose $PROFILE -f docker-compose.integration.yml up \
239+
--build \
240+
--abort-on-container-exit \
241+
integration-tests
242+
fi
221243
EXIT_CODE=$?
222244

245+
# Verify coverage was generated
246+
if [ -f "$PROJECT_ROOT/coverage/integration-lcov.info" ]; then
247+
log_success "Coverage generated: $(wc -l < "$PROJECT_ROOT/coverage/integration-lcov.info") lines"
248+
else
249+
log_warn "Coverage file not found at $PROJECT_ROOT/coverage/integration-lcov.info"
250+
fi
251+
223252
if [ $EXIT_CODE -eq 0 ]; then
224253
log_success "Integration tests passed!"
225254
else

tests/integration/common/registry.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ impl TestRegistry {
108108
pub fn get_network(&self, network: &str) -> Result<&NetworkConfig> {
109109
self.networks
110110
.get(network)
111-
.ok_or_else(|| eyre::eyre!("Network '{}' not found in registry", network))
111+
.ok_or_else(|| eyre::eyre!("Network '{}' not found in registry!", network))
112112
}
113113

114114
/// Get contract address for a network

0 commit comments

Comments
 (0)