Skip to content
Open
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
19 changes: 4 additions & 15 deletions .github/workflows/docker-image.yml
Original file line number Diff line number Diff line change
Expand Up @@ -66,25 +66,14 @@ jobs:
with:
dotnet-version: '10.0.x'

- name: Run HTTP-level tests against the image
- name: Run integration tests (HTTP-level + real Chorus client) against the image
working-directory: csharp
env:
# The test fixture drives a container via this CLI (docker on the runner); it starts/stops
# the pre-built image itself, so we skip the fixture's own build step.
HGRESUME_PODMAN: docker
# The test fixture drives the pre-built image itself via Testcontainers (against the
# runner's local Docker daemon), so we skip the fixture's own build step.
HGRESUME_IMAGE: hgresume-csharp:test
HGRESUME_SKIP_BUILD: '1'
HGRESUME_PORT: '8034'
run: dotnet test test/HgResume.HttpTests/HgResume.HttpTests.csproj --logger "console;verbosity=normal"

- name: Run send/receive tests (real Chorus client) against the image
working-directory: csharp
env:
HGRESUME_PODMAN: docker
HGRESUME_IMAGE: hgresume-csharp:test
HGRESUME_SKIP_BUILD: '1'
HGRESUME_PORT: '8041'
run: dotnet test test/HgResume.SendReceiveTests/HgResume.SendReceiveTests.csproj --logger "console;verbosity=normal"
run: dotnet test test/HgResume.IntegrationTests/HgResume.IntegrationTests.csproj --logger "console;verbosity=normal"

- name: Log in to the Container registry
# Fork PRs get a read-only GITHUB_TOKEN; skip login/push so the job still builds without a 403.
Expand Down
16 changes: 10 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,9 @@ The original PHP implementation is no longer in this tree. The last commit that
- `AsyncRunner` — runs long hg commands in the background and signals completion via a `.async_run`
file, so a later HTTP request can observe the result (this is what makes transfers resumable).
- `BundleHelper` — per-transaction state + metadata (stored as JSON).
- `csharp/test/HgResume.HttpTests/` — HTTP-level xUnit tests. They drive the **running container**
over HTTP (via a podman-managed fixture) and assert on the protocol.
- `csharp/test/HgResume.IntegrationTests/` — xUnit tests that drive the **running container** (via a
podman-managed fixture): HTTP-level wire-protocol tests, and end-to-end send/receive tests using the
real Chorus resumable client.
- `csharp/Dockerfile` — multi-stage `dotnet/sdk:10.0` → `dotnet/aspnet:10.0`, installs `mercurial`.
Listens on port 80 and exposes `/var/cache/hgresume` and `/var/vcs/public`.
- `docker-compose.yaml` — local run against a host Mercurial repo tree.
Expand Down Expand Up @@ -53,16 +54,19 @@ curl -i http://localhost:8034/api/v03/isAvailable

## Tests

The HTTP-level suite builds the image, runs it in a container, seeds fixture repos, and exercises
the protocol end-to-end:
The integration suite builds the image, runs it in a container, seeds fixture repos, and exercises
the protocol end-to-end — both directly over HTTP and via the real Chorus resumable client:

```bash
cd csharp
./run-tests.sh # or: pwsh ./run-tests.ps1
```

Useful env overrides: `HGRESUME_IMAGE`, `HGRESUME_PORT`, `HGRESUME_SKIP_BUILD`, and
`HGRESUME_BASE_URL` + `HGRESUME_CONTAINER` (to run the tests against an already-running container).
Useful env overrides: `HGRESUME_IMAGE`, `HGRESUME_SKIP_BUILD`, and `HGRESUME_BASE_URL` +
`HGRESUME_CONTAINER` (to run the tests against an already-running container). The suite drives the
container via [Testcontainers](https://testcontainers.com/), which needs a Docker-API-compatible
endpoint — Docker Desktop/Engine work out of the box; podman needs its API socket exposed and
`DOCKER_HOST` pointed at it.

## CI

Expand Down
6 changes: 3 additions & 3 deletions csharp/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@ obj/
.idea/
.vs/

# Bundled Mercurial dropped into the SendReceive test project by SIL.Chorus.Mercurial at build time
test/HgResume.SendReceiveTests/Mercurial/
test/HgResume.SendReceiveTests/MercurialExtensions/
# Bundled Mercurial dropped into the integration test project by SIL.Chorus.Mercurial at build time
test/HgResume.IntegrationTests/Mercurial/
test/HgResume.IntegrationTests/MercurialExtensions/
3 changes: 1 addition & 2 deletions csharp/HgResume.slnx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
<Project Path="src/HgResume.Api/HgResume.Api.csproj" />
</Folder>
<Folder Name="/test/">
<Project Path="test/HgResume.HttpTests/HgResume.HttpTests.csproj" />
<Project Path="test/HgResume.SendReceiveTests/HgResume.SendReceiveTests.csproj" />
<Project Path="test/HgResume.IntegrationTests/HgResume.IntegrationTests.csproj" />
</Folder>
</Solution>
12 changes: 6 additions & 6 deletions csharp/run-tests.ps1
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
#!/usr/bin/env pwsh
# Builds the C# hgresume image with podman, then runs the HTTP-level test suite against a container.
# The test fixture starts/stops the container itself; this script just builds the image first.
# Builds the C# hgresume image with podman, then runs the integration test suite against a container.
# The test fixture (Testcontainers) starts/stops the container itself; this script just builds the
# image first. Testcontainers talks to the Docker Engine API directly, so this only works if podman's
# API socket is exposed and DOCKER_HOST points at it (Docker Desktop/Engine need no extra setup).
param(
[string]$Image = "hgresume-csharp:test",
[string]$Port = "8034",
[switch]$SkipBuild
)

Expand All @@ -20,11 +21,10 @@ try {
}

$env:HGRESUME_IMAGE = $Image
$env:HGRESUME_PORT = $Port
$env:HGRESUME_SKIP_BUILD = "1" # already built above

Write-Host "==> Running HTTP-level tests against the image" -ForegroundColor Cyan
dotnet test test/HgResume.HttpTests/HgResume.HttpTests.csproj --logger "console;verbosity=normal"
Write-Host "==> Running integration tests against the image" -ForegroundColor Cyan
dotnet test test/HgResume.IntegrationTests/HgResume.IntegrationTests.csproj --logger "console;verbosity=normal"
if ($LASTEXITCODE -ne 0) {
throw "dotnet test failed with exit code $LASTEXITCODE"
}
Expand Down
12 changes: 6 additions & 6 deletions csharp/run-tests.sh
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
#!/usr/bin/env bash
# Builds the C# hgresume image with podman, then runs the HTTP-level test suite against a container.
# The test fixture starts/stops the container itself; this script just builds the image first.
# Builds the C# hgresume image with podman, then runs the integration test suite against a container.
# The test fixture (Testcontainers) starts/stops the container itself; this script just builds the
# image first. Testcontainers talks to the Docker Engine API directly, so this only works if podman's
# API socket is exposed and DOCKER_HOST points at it (Docker Desktop/Engine need no extra setup).
set -euo pipefail

IMAGE="${HGRESUME_IMAGE:-hgresume-csharp:test}"
PORT="${HGRESUME_PORT:-8034}"
here="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
cd "$here"

Expand All @@ -14,8 +15,7 @@ if [ "${1:-}" != "--skip-build" ]; then
fi

export HGRESUME_IMAGE="$IMAGE"
export HGRESUME_PORT="$PORT"
export HGRESUME_SKIP_BUILD=1

echo "==> Running HTTP-level tests against the image"
dotnet test test/HgResume.HttpTests/HgResume.HttpTests.csproj --logger "console;verbosity=normal"
echo "==> Running integration tests against the image"
dotnet test test/HgResume.IntegrationTests/HgResume.IntegrationTests.csproj --logger "console;verbosity=normal"
15 changes: 12 additions & 3 deletions csharp/src/HgResume.Api/HgResumeApi.cs
Original file line number Diff line number Diff line change
Expand Up @@ -447,11 +447,20 @@ private string GetRepoPath(string repoId)
foreach (var basePath in _config.RepoSearchPaths)
{
var fullBasePath = Path.GetFullPath(basePath);
var possibleRepoPath = Path.Combine(fullBasePath, repoId);
var flatPath = Path.Combine(fullBasePath, repoId);

if (possibleRepoPath.StartsWith(fullBasePath) && Directory.Exists(possibleRepoPath))
if (flatPath.StartsWith(fullBasePath) && Directory.Exists(flatPath))
{
return possibleRepoPath;
return flatPath;
}

// LexBox/manage-API layout nests repos one level under their first character (e.g.
// {root}/s/sample-hg-repo, see RepoManageService.PrefixRepoFilePath). Repos LexBox
// provisions via /api/manage live there, so check it too before giving up.
var nestedPath = Path.Combine(fullBasePath, repoId[0].ToString(), repoId);
if (nestedPath.StartsWith(fullBasePath) && Directory.Exists(nestedPath))
{
return nestedPath;
}
}
return "";
Expand Down
26 changes: 25 additions & 1 deletion csharp/src/HgResume.Api/Manage/ManageRepoEndpoints.cs
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,31 @@ private static async Task<Results<NoContent, ValidationProblem>> FinishReset(
max.MaxRequestBodySize = null;
}

await repos.FinishReset(projectCode, request.Body, cancellationToken);
// ZipArchive needs a seekable stream (to read the central directory), and request.Body is
// neither seekable nor safe to read synchronously, so buffer it first. Repo zips can be
// large, so buffer to a temp file rather than to memory.
var zipPath = Path.Combine(Path.GetTempPath(), $"hgresume-finish-reset-{Guid.NewGuid():N}.zip");
try
{
await using (var zipBuffer = new FileStream(
zipPath,
FileMode.CreateNew,
FileAccess.ReadWrite,
FileShare.None,
bufferSize: 64 * 1024,
FileOptions.Asynchronous | FileOptions.SequentialScan))
{
await request.Body.CopyToAsync(zipBuffer, cancellationToken);
zipBuffer.Position = 0;

await repos.FinishReset(projectCode, zipBuffer, cancellationToken);
}
}
finally
{
File.Delete(zipPath);
}

return TypedResults.NoContent();
}

Expand Down
21 changes: 0 additions & 21 deletions csharp/test/HgResume.HttpTests/HgResume.HttpTests.csproj

This file was deleted.

Loading