-
Notifications
You must be signed in to change notification settings - Fork 25
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add end-to-end tests #33
Changes from all commits
bc8df92
b6dec91
e8a38b0
368fa56
c9ee08f
9c7860f
704f46f
a6ff664
f0b17de
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
name: End-to-end test | ||
|
||
on: | ||
pull_request: | ||
workflow_dispatch: | ||
inputs: | ||
run-all: | ||
type: boolean | ||
description: 'Include tests that are excluded by default due to slowness' | ||
default: false | ||
|
||
jobs: | ||
e2e-test: | ||
runs-on: ubuntu-latest | ||
|
||
steps: | ||
- name: Clone repository | ||
uses: actions/checkout@v3 | ||
|
||
- name: Setup Go | ||
uses: actions/setup-go@v3 | ||
with: | ||
go-version-file: 'go.mod' | ||
|
||
- name: Setup Node.js | ||
uses: actions/setup-node@v3 | ||
with: | ||
node-version: 18 | ||
|
||
- name: Install system dependencies for end-to-end tests | ||
run: build/ci/install-dependencies.sh | ||
shell: bash | ||
|
||
- name: Enable perf tests | ||
if: ${{ github.event.inputs.run-all }} | ||
run: echo "E2E_FLAGS='--all'" >> $GITHUB_ENV | ||
|
||
- name: Run end-to-end tests | ||
env: | ||
E2E_FLAGS: ${{env.E2E_FLAGS}} | ||
run: make e2e-test E2E_FLAGS="$E2E_FLAGS" |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,3 +3,5 @@ | |
/bin/ | ||
/_dist/ | ||
/_docs/ | ||
/_test/ | ||
node_modules/ |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
#!/bin/bash | ||
die () { | ||
echo "$*" >&2 | ||
exit 1 | ||
} | ||
|
||
# Exit as soon as any line fails | ||
set -e | ||
|
||
# Install latest version of Git (minimum v2.40) | ||
if command -v apt >/dev/null 2>&1; then | ||
sudo add-apt-repository ppa:git-core/ppa | ||
sudo apt update | ||
sudo apt -q -y install git | ||
elif command -v brew >/dev/null 2>&1; then | ||
brew install git | ||
else | ||
die 'Cannot install git' | ||
fi | ||
|
||
# Set up test Git config | ||
git config --global user.name "GitHub Action" | ||
git config --global user.email "[email protected]" |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
#!/bin/bash | ||
|
||
THISDIR="$( cd "$(dirname "$0")" ; pwd -P )" | ||
TESTDIR="$THISDIR/../test/e2e" | ||
|
||
# Defaults | ||
ARGS=() | ||
|
||
# Parse script arguments | ||
for i in "$@" | ||
do | ||
case "$i" in | ||
--offline) | ||
ARGS+=("-p" "offline") | ||
shift # past argument | ||
;; | ||
--all) | ||
ARGS+=("-p" "all") | ||
shift # past argument | ||
;; | ||
*) | ||
die "unknown option '$i'" | ||
;; | ||
esac | ||
done | ||
|
||
# Exit as soon as any line fails | ||
set -e | ||
|
||
cd "$TESTDIR" | ||
|
||
npm install | ||
npm run test -- ${ARGS:+${ARGS[*]}} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
const common = { | ||
requireModule: ['ts-node/register'], | ||
require: ['features/**/*.ts'], | ||
publishQuiet: true, | ||
format: ['progress'], | ||
formatOptions: { | ||
snippetInterface: 'async-await' | ||
}, | ||
worldParameters: { | ||
bundleServerCommand: '../../bin/git-bundle-server', | ||
bundleWebServerCommand: '../../bin/git-bundle-web-server', | ||
trashDirectoryBase: '../../_test/e2e' | ||
} | ||
} | ||
|
||
module.exports = { | ||
default: { | ||
...common, | ||
tags: 'not @slow', | ||
}, | ||
offline: { | ||
...common, | ||
tags: 'not @online', | ||
}, | ||
all: { | ||
...common | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
Feature: Basic bundle server usage | ||
|
||
Background: The bundle web server is running | ||
Given the bundle web server was started at port 8080 | ||
|
||
@online | ||
Scenario: A user can clone with a bundle URI pointing to the bundle server | ||
Given a remote repository 'https://github.com/vdye/asset-hash.git' | ||
Given the bundle server has been initialized with the remote repo | ||
When I clone from the remote repo with a bundle URI | ||
Then bundles are downloaded and used | ||
Comment on lines
+7
to
+11
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is an interesting formal mechanism for describing tests. Each of these lines are repeatable steps that can be mixed and matched. For instance, I could imagine (not looking ahead) a test like:
The only new step to build is the |
||
|
||
Scenario: A user can fetch with a bundle server that's behind and get all updates | ||
Given a new remote repository with main branch 'main' | ||
Given another user pushed 10 commits to 'main' | ||
Given the bundle server has been initialized with the remote repo | ||
Given I cloned from the remote repo with a bundle URI | ||
Given another user pushed 2 commits to 'main' | ||
When I fetch from the remote | ||
Then I am up-to-date with 'main' | ||
Then my repo's bundles are not up-to-date with 'main' | ||
|
||
Scenario: A user will fetch incremental bundles to stay up-to-date | ||
Given a new remote repository with main branch 'main' | ||
Given another user pushed 10 commits to 'main' | ||
Given the bundle server has been initialized with the remote repo | ||
Given I cloned from the remote repo with a bundle URI | ||
Given another user pushed 2 commits to 'main' | ||
Given the bundle server was updated for the remote repo | ||
When I fetch from the remote | ||
Then I am up-to-date with 'main' | ||
Then my repo's bundles are up-to-date with 'main' | ||
|
||
Scenario: A user can fetch force-pushed refs from the bundle server | ||
Given a new remote repository with main branch 'main' | ||
Given another user pushed 10 commits to 'main' | ||
Given the bundle server has been initialized with the remote repo | ||
Given I cloned from the remote repo with a bundle URI | ||
Given another user removed 2 commits and added 4 commits to 'main' | ||
Given the bundle server was updated for the remote repo | ||
When I fetch from the remote | ||
Then I am up-to-date with 'main' | ||
Then my repo's bundles are up-to-date with 'main' | ||
Comment on lines
+13
to
+43
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 😍 this is amazing to read and clearly understand the scenarios. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
import { randomBytes } from 'crypto' | ||
import * as child_process from 'child_process' | ||
import { RemoteRepo } from './remote' | ||
|
||
export class BundleServer { | ||
private bundleServerCmd: string | ||
private bundleWebServerCmd: string | ||
|
||
// Web server | ||
private webServerProcess: child_process.ChildProcess | undefined | ||
private bundleUriBase: string | undefined | ||
|
||
// Remote repo info (for now, only support one per test) | ||
private route: string | undefined | ||
|
||
constructor(bundleServerCmd: string, bundleWebServerCmd: string) { | ||
this.bundleServerCmd = bundleServerCmd | ||
this.bundleWebServerCmd = bundleWebServerCmd | ||
} | ||
|
||
startWebServer(port: number): void { | ||
if (this.webServerProcess) { | ||
throw new Error("Tried to start web server, but web server is already running") | ||
} | ||
this.webServerProcess = child_process.spawn(this.bundleWebServerCmd, ["--port", String(port)]) | ||
this.bundleUriBase = `http://localhost:${port}/` | ||
} | ||
|
||
init(remote: RemoteRepo): child_process.SpawnSyncReturns<Buffer> { | ||
this.route = `e2e/${randomBytes(8).toString('hex')}` | ||
return child_process.spawnSync(this.bundleServerCmd, ["init", remote.remoteUri, this.route]) | ||
} | ||
|
||
update(): child_process.SpawnSyncReturns<Buffer> { | ||
if (!this.route) { | ||
throw new Error("Tried to update server before running 'init'") | ||
} | ||
return child_process.spawnSync(this.bundleServerCmd, ["update", this.route]) | ||
} | ||
|
||
bundleUri(): string { | ||
if (!this.webServerProcess) { | ||
throw new Error("Tried to get bundle URI before starting the web server") | ||
} | ||
if (!this.route) { | ||
throw new Error("Tried to get bundle URI before running 'init'") | ||
} | ||
|
||
return this.bundleUriBase + this.route | ||
} | ||
|
||
cleanup(): void { | ||
if (this.webServerProcess) { | ||
const killed = this.webServerProcess.kill('SIGINT') | ||
if (!killed) { | ||
console.warn("Web server process was not successfully stopped") | ||
} | ||
} | ||
|
||
// Delete the added route | ||
if (this.route) { | ||
child_process.spawnSync(this.bundleServerCmd, ["delete", this.route]) | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As you mentioned, "only on PRs" is quite nice because that way it's not on arbitrary pushes and not re-testing after a PR merged (and was subject to required builds).
The manual trigger of the workflow (with optional perf tests!) is excellent.