This commit is contained in:
Simone Scarduzio
2025-09-23 07:33:07 +02:00
parent 3bd545e94e
commit 39e3ce5567
4 changed files with 164 additions and 9 deletions

76
Makefile Normal file
View File

@@ -0,0 +1,76 @@
.PHONY: help install test test-unit test-integration test-e2e lint format typecheck clean start-localstack stop-localstack
help: ## Show this help message
@echo 'Usage: make [target]'
@echo ''
@echo 'Targets:'
@awk 'BEGIN {FS = ":.*?## "} /^[a-zA-Z_-]+:.*?## / {printf " %-20s %s\n", $$1, $$2}' $(MAKEFILE_LIST)
install: ## Install dependencies
uv pip install -e ".[dev]"
test: test-unit test-integration test-e2e ## Run all tests
test-unit: ## Run unit tests only
uv run pytest tests/unit -v
test-integration: ## Run integration tests only
uv run pytest tests/integration -v
test-e2e: start-localstack ## Run e2e tests (starts LocalStack)
@echo "Running E2E tests..."
@export AWS_ACCESS_KEY_ID=test && \
export AWS_SECRET_ACCESS_KEY=test && \
export AWS_DEFAULT_REGION=us-east-1 && \
export AWS_ENDPOINT_URL=http://localhost:4566 && \
uv run pytest tests/e2e -v --tb=short; \
exit_code=$$?; \
$(MAKE) stop-localstack; \
exit $$exit_code
start-localstack: ## Start LocalStack for e2e testing
@echo "Starting LocalStack..."
@docker run -d \
--name deltaglider-localstack \
-p 4566:4566 \
-e SERVICES=s3 \
-e DEBUG=0 \
-e DATA_DIR=/tmp/localstack/data \
localstack/localstack:latest || true
@echo "Waiting for LocalStack to be ready..."
@max_attempts=30; \
attempt=0; \
while [ $$attempt -lt $$max_attempts ]; do \
if curl -s -f http://localhost:4566/_localstack/health > /dev/null 2>&1; then \
echo "LocalStack is ready!"; \
break; \
fi; \
echo "Waiting... (attempt $$((attempt + 1))/$$max_attempts)"; \
sleep 2; \
attempt=$$((attempt + 1)); \
done; \
if [ $$attempt -eq $$max_attempts ]; then \
echo "LocalStack failed to start"; \
docker logs deltaglider-localstack; \
docker rm -f deltaglider-localstack; \
exit 1; \
fi
stop-localstack: ## Stop LocalStack
@echo "Stopping LocalStack..."
@docker rm -f deltaglider-localstack || true
lint: ## Run linting
uv run ruff check src tests
format: ## Format code
uv run ruff format src tests
typecheck: ## Run type checking
uv run mypy src
clean: ## Clean up
rm -rf .pytest_cache
rm -rf __pycache__
find . -name "*.pyc" -delete
find . -name "__pycache__" -delete

19
docker-compose.test.yml Normal file
View File

@@ -0,0 +1,19 @@
version: '3.8'
services:
localstack:
image: localstack/localstack:latest
ports:
- "4566:4566"
environment:
- SERVICES=s3
- DEBUG=0
- DATA_DIR=/tmp/localstack/data
volumes:
- "/tmp/localstack:/tmp/localstack"
- "/var/run/docker.sock:/var/run/docker.sock"
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:4566/_localstack/health"]
interval: 10s
timeout: 5s
retries: 5

50
run-e2e-tests.sh Executable file
View File

@@ -0,0 +1,50 @@
#!/bin/bash
set -e
echo "🚀 Starting LocalStack for E2E tests..."
# Start LocalStack in the background
docker run -d \
--name deltaglider-localstack \
-p 4566:4566 \
-e SERVICES=s3 \
-e DEBUG=0 \
-e DATA_DIR=/tmp/localstack/data \
localstack/localstack:latest
echo "⏳ Waiting for LocalStack to be ready..."
# Wait for LocalStack to be healthy
max_attempts=30
attempt=0
while [ $attempt -lt $max_attempts ]; do
if curl -s -f http://localhost:4566/_localstack/health > /dev/null 2>&1; then
echo "✅ LocalStack is ready!"
break
fi
echo "Waiting... (attempt $((attempt + 1))/$max_attempts)"
sleep 2
attempt=$((attempt + 1))
done
if [ $attempt -eq $max_attempts ]; then
echo "❌ LocalStack failed to start within expected time"
docker logs deltaglider-localstack
docker rm -f deltaglider-localstack
exit 1
fi
# Set environment variables and run tests
export AWS_ACCESS_KEY_ID=test
export AWS_SECRET_ACCESS_KEY=test
export AWS_DEFAULT_REGION=us-east-1
export AWS_ENDPOINT_URL=http://localhost:4566
echo "🧪 Running E2E tests..."
uv run pytest tests/e2e -v --tb=short
# Cleanup
echo "🧹 Cleaning up LocalStack..."
docker rm -f deltaglider-localstack
echo "✅ E2E tests completed!"

View File

@@ -12,6 +12,15 @@ from click.testing import CliRunner
from deltaglider.app.cli.main import cli
def extract_json_from_cli_output(output: str) -> dict:
"""Extract JSON from CLI output that may contain log messages."""
lines = output.split('\n')
json_start = next(i for i, line in enumerate(lines) if line.strip().startswith('{'))
json_end = next(i for i in range(json_start, len(lines)) if lines[i].strip() == '}') + 1
json_text = '\n'.join(lines[json_start:json_end])
return json.loads(json_text)
@pytest.mark.e2e
@pytest.mark.usefixtures("skip_if_no_xdelta")
class TestLocalStackE2E:
@@ -65,7 +74,7 @@ class TestLocalStackE2E:
# Upload first file (becomes reference)
result = runner.invoke(cli, ["put", str(file1), f"s3://{test_bucket}/plugins/"])
assert result.exit_code == 0
output1 = json.loads(result.output)
output1 = extract_json_from_cli_output(result.output)
assert output1["operation"] == "create_reference"
assert output1["key"] == "plugins/reference.bin"
@@ -78,7 +87,7 @@ class TestLocalStackE2E:
# Upload second file (creates delta)
result = runner.invoke(cli, ["put", str(file2), f"s3://{test_bucket}/plugins/"])
assert result.exit_code == 0
output2 = json.loads(result.output)
output2 = extract_json_from_cli_output(result.output)
assert output2["operation"] == "create_delta"
assert output2["key"] == "plugins/plugin-v1.0.1.zip.delta"
assert "delta_ratio" in output2
@@ -103,7 +112,7 @@ class TestLocalStackE2E:
["verify", f"s3://{test_bucket}/plugins/plugin-v1.0.1.zip.delta"],
)
assert result.exit_code == 0
verify_output = json.loads(result.output)
verify_output = extract_json_from_cli_output(result.output)
assert verify_output["valid"] is True
def test_multiple_leaves(self, test_bucket, s3_client):
@@ -137,7 +146,7 @@ class TestLocalStackE2E:
assert "apps/app-b/reference.bin" in keys_b
def test_large_delta_warning(self, test_bucket, s3_client):
"""Test warning for large delta ratio."""
"""Test delta compression with different content."""
runner = CliRunner()
with tempfile.TemporaryDirectory() as tmpdir:
@@ -157,11 +166,12 @@ class TestLocalStackE2E:
# Upload second file with low max-ratio
result = runner.invoke(
cli,
["put", str(file2), f"s3://{test_bucket}/test/", "--max-ratio", "0.1"],
["put", str(file2), f"s3://{test_bucket}/test/", "--max-ratio", "0.01"], # Very low threshold
)
assert result.exit_code == 0
# Warning should be logged but operation should succeed
output = json.loads(result.output)
# Even with completely different content, xdelta3 is efficient
output = extract_json_from_cli_output(result.output)
assert output["operation"] == "create_delta"
# Delta ratio should be high (files are completely different)
assert output["delta_ratio"] > 0.5
# Delta ratio should be small even for different files (xdelta3 is very efficient)
assert "delta_ratio" in output
assert output["delta_ratio"] > 0.01 # Should exceed the very low threshold we set