Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions .github/workflows/integration-tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
name: Integration Tests

on:
push:
branches: [main]
pull_request:
branches: [main]

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
integration:
name: Integration Tests
runs-on: ubuntu-latest
timeout-minutes: 30

steps:
- uses: actions/checkout@v4

- name: Install uv
uses: astral-sh/setup-uv@v5

- name: Set up Python
run: uv python install

- name: Install dependencies
run: uv sync --dev

- name: Run integration tests
run: uv run pytest tests/ -v --log-cli-level=INFO -m integration
38 changes: 38 additions & 0 deletions .github/workflows/python-lint.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
name: Python Lint

on:
push:
branches: [main]
pull_request:
branches: [main]

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
lint:
name: Ruff & Pyright
runs-on: ubuntu-latest
timeout-minutes: 10

steps:
- uses: actions/checkout@v4

- name: Install uv
uses: astral-sh/setup-uv@v5

- name: Set up Python
run: uv python install

- name: Install dependencies
run: uv sync --dev

- name: Ruff check (lint)
run: uv run ruff check .

- name: Ruff format (formatting check)
run: uv run ruff format --check .

- name: Pyright (type check)
run: uv run pyright
11 changes: 5 additions & 6 deletions .github/workflows/shellcheck.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ name: ShellCheck
on:
push:
branches: [main]
paths: ["**.sh"]
paths: ["**.sh", "**.bash", "**.zsh", "**.ksh"]
pull_request:
branches: [main]
paths: ["**.sh"]
paths: ["**.sh", "**.bash", "**.zsh", "**.ksh"]

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
Expand All @@ -19,13 +19,12 @@ jobs:
steps:
- uses: actions/checkout@v4

- name: Run ShellCheck (strictest settings)
- name: Run ShellCheck
run: |
shopt -s globstar nullglob
shellcheck --version
shellcheck \
--shell=sh \
--severity=style \
--enable=all \
--external-sources \
--format=gcc \
./install.sh
**/*.sh **/*.bash **/*.zsh **/*.ksh
8 changes: 6 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@
# Agent Blame local database (legacy)
.agentblame/

# OpenCode local plugin (installed by agentblame init)
.opencode/
!.opencode/

# Python
__pycache__/
.pytest_cache/
*.pyc
.venv/
1 change: 1 addition & 0 deletions .python-version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
3.14
56 changes: 56 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
[project]
name = "git-fs-tests"
version = "0.0.0"
description = "Integration tests for git-fs (Rust/FUSE). Not a Python package."
requires-python = ">=3.14"
dependencies = []

[dependency-groups]
dev = [
"pytest>=9.0.2",
"pytest-timeout>=2.4.0",
"pytest-in-docker>=0.2.1",
"pytest-xdist>=3.5.0",
"ruff>=0.9.0",
"pyright>=1.1.390",
]

[tool.pytest.ini_options]
testpaths = ["tests"]
timeout = 300
addopts = "-n auto"
markers = [
"integration: marks tests as integration tests",
]

[tool.ruff]
target-version = "py314"
line-length = 88

[tool.ruff.lint]
select = ["ALL"]
ignore = [
"COM812", # Conflicts with formatter
"D203", # Conflicts with D211 which is better
"D213", # Conflicts with D212 which is better
]

[tool.ruff.lint.per-file-ignores]
"tests/*" = [
"S", # It's tests, no need for security
"PLC0415", # RPyC teleport requires imports inside function body
]

[tool.ruff.format]
quote-style = "double"
indent-style = "space"

[tool.pyright]
pythonVersion = "3.14"
typeCheckingMode = "strict"
include = ["tests"]
reportMissingTypeStubs = false
reportUnknownMemberType = false
reportUnknownVariableType = false
reportUnknownArgumentType = false
reportUnknownParameterType = false
1 change: 1 addition & 0 deletions tests/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Integration tests for git-fs."""
54 changes: 54 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
"""Shared setup for git-fs integration tests."""

from __future__ import annotations

import contextlib
import subprocess
import time
from pathlib import Path
from typing import TYPE_CHECKING

from testcontainers.core.container import DockerContainer

if TYPE_CHECKING:
from collections.abc import Iterator

IMAGE_TAG = "git-fs-test:latest"
REPO_ROOT = Path(__file__).resolve().parent.parent

_GITFS_READY_TIMEOUT = 60
_GITFS_READY_POLL_INTERVAL = 2


@contextlib.contextmanager
def gitfs_container_factory(port: int) -> Iterator[DockerContainer]:
"""Create a privileged container with git-fs mounted and ready."""
subprocess.run(
[
"docker",
"build",
"-f",
str(REPO_ROOT / "tests/docker/Dockerfile"),
"-t",
IMAGE_TAG,
str(REPO_ROOT),
],
check=True,
timeout=600,
)

container = (
DockerContainer(IMAGE_TAG).with_kwargs(privileged=True).with_exposed_ports(port)
)
with container:
deadline = time.monotonic() + _GITFS_READY_TIMEOUT
while time.monotonic() < deadline:
exit_code, _ = container.exec(["test", "-f", "/tmp/git-fs-ready"])
if exit_code == 0:
break
time.sleep(_GITFS_READY_POLL_INTERVAL)
else:
msg = f"git-fs mount did not become ready within {_GITFS_READY_TIMEOUT}s"
raise TimeoutError(msg)

yield container
37 changes: 37 additions & 0 deletions tests/docker/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Stage 1: Build the git-fs binary
FROM rust:1.93-bookworm AS builder

RUN apt-get update && apt-get install -y \
pkg-config \
libfuse3-dev \
&& rm -rf /var/lib/apt/lists/*

WORKDIR /src
COPY Cargo.toml Cargo.lock build.rs ./
COPY .git/ .git/
COPY src/ src/

RUN cargo build --release

# Stage 2: Runtime image
FROM python:3.14-slim-bookworm

RUN apt-get update && apt-get install -y \
fuse3 \
ca-certificates \
findutils \
git \
&& rm -rf /var/lib/apt/lists/*

# Allow non-root users to mount FUSE filesystems
RUN sed -i 's/#user_allow_other/user_allow_other/' /etc/fuse.conf || true

COPY --from=builder /src/target/release/git-fs /usr/local/bin/git-fs

RUN mkdir -p /mnt/git-fs /etc/git-fs
COPY tests/docker/config.toml /etc/git-fs/config.toml
COPY tests/docker/entrypoint.sh /usr/local/bin/entrypoint.sh
RUN chmod +x /usr/local/bin/entrypoint.sh

ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]
CMD ["sleep", "infinity"]
9 changes: 9 additions & 0 deletions tests/docker/config.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
mount-point = "/mnt/git-fs"
uid = 0
gid = 0

[cache]
path = "/tmp/git-fs-cache"

[daemon]
pid-file = "/tmp/git-fs.pid"
17 changes: 17 additions & 0 deletions tests/docker/entrypoint.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#!/bin/sh
GIT_FS_LOG=debug nohup git-fs --config-path /etc/git-fs/config.toml run \
> /tmp/git-fs-stdout.log 2> /tmp/git-fs-stderr.log &

# Wait for FUSE mount, then hand off to CMD
elapsed=0
while [ "${elapsed}" -lt 60 ]; do
if [ -d /mnt/git-fs/github ]; then
touch /tmp/git-fs-ready
exec "$@"
fi
sleep 2
elapsed=$((elapsed + 2))
done

echo "git-fs mount did not become ready within 60s" >&2
exit 1
Loading