Skip to main content

ci: set up a basic framework for running tests in Git repos

ID
04a9d5e
date
2026-05-14 06:04:10+00:00
author
Alex Chan <alex@alexwlchan.net>
parent
2b28cab
message
ci: set up a basic framework for running tests in Git repos
changed files
5 files, 183 additions

Changed files

ci/README.md (0) → ci/README.md (1926)

diff --git a/ci/README.md b/ci/README.md
new file mode 100644
index 0000000..8b80f91
--- /dev/null
+++ b/ci/README.md
@@ -0,0 +1,82 @@
+# ci
+
+This is a collection of scripts that I use for a DIY continuous integration system for my repos.
+
+Within a bare repo, it's enough to call the `run_post_receive_checks.sh` wrapper, for example:
+
+```bash
+#!/usr/bin/env bash
+
+set -o errexit
+set -o nounset
+
+bash ~/repos/scripts/ci/run_post_receive_checks.sh chives
+```
+
+## The individual scripts
+
+<!-- [[[cog
+
+# This adds the root of the repo to the PATH, which has cog_helpers.py
+from os.path import abspath, dirname
+import sys
+
+sys.path.append(abspath(dirname(dirname("."))))
+
+import cog_helpers
+
+folder_name = "ci"
+
+scripts = [
+    {
+        "usage": "run_post_receive_checks.sh [REPO_NAME]",
+        "description": """
+        run the post-receive checks for a repo, dumping the output to a file
+        """
+    },
+    {
+        "usage": "post_receive_tests.sh [REPO_NAME]",
+        "description": """
+        run the tests for a repo, printing output to stdout/stderr
+        """
+    },
+    {
+        "usage": "ts",
+        "description": """
+        find the `run_{name}_tests.sh` script in a repo and run it
+        """
+    },
+]
+
+cog_helpers.create_description_table(folder_name=folder_name, scripts=scripts)
+
+]]]-->
+<dl>
+  <dt>
+    <a href="https://github.com/alexwlchan/scripts/blob/main/ci/run_post_receive_checks.sh">
+      <code>run_post_receive_checks.sh [REPO_NAME]</code>
+    </a>
+  </dt>
+  <dd>
+    run the post-receive checks for a repo, dumping the output to a file
+  </dd>
+
+  <dt>
+    <a href="https://github.com/alexwlchan/scripts/blob/main/ci/post_receive_tests.sh">
+      <code>post_receive_tests.sh [REPO_NAME]</code>
+    </a>
+  </dt>
+  <dd>
+    run the tests for a repo, printing output to stdout/stderr
+  </dd>
+
+  <dt>
+    <a href="https://github.com/alexwlchan/scripts/blob/main/ci/ts">
+      <code>ts</code>
+    </a>
+  </dt>
+  <dd>
+    find the `run_{name}_tests.sh` script in a repo and run it
+  </dd>
+</dl>
+<!-- [[[end]]] (sum: MvYcRvOIeM) -->

ci/post_receive_tests.sh (0) → ci/post_receive_tests.sh (1727)

diff --git a/ci/post_receive_tests.sh b/ci/post_receive_tests.sh
new file mode 100755
index 0000000..08455e6
--- /dev/null
+++ b/ci/post_receive_tests.sh
@@ -0,0 +1,68 @@
+#!/usr/bin/env bash
+# Run the post-receive tests for a repository, which means:
+#
+#   1. Cloning the repo to a scratch directory
+#   2. Setting up a virtualenv/dependencies
+#   3. Running the test suite through `ts`
+#
+# TODO: Support running tests on the non-main branch.
+
+set -o errexit
+set -o nounset
+
+if (( $# != 1 )); then
+  echo "Usage: $0 REPO_NAME" >&2
+  exit 1
+fi
+
+# Print a command in blue, then run the command
+run_command() {
+    echo -e "\033[34m-> $@\033[0m"
+    bash -c "$@"
+}
+
+unset GIT_DIR
+
+# REPO_NAME is the name of the repository to run tests for
+REPO_NAME="$1"
+
+# REMOTES_ROOT is the directory where I keep all my remote repos
+REMOTES_ROOT="/Volumes/Media (Speedwell)/Media/repos"
+
+# SCRATCH_ROOT is the directory where I have checked-out versions for
+# running post-receive tests
+SCRATCH_ROOT="$REMOTES_ROOT/.working"
+
+export REPO_DIR="$REMOTES_ROOT/$REPO_NAME"
+export SCRATCH_DIR="$SCRATCH_ROOT/$REPO_NAME"
+
+export CI=true
+
+# Clone the Git repo to the directory if it doesn't exist yet, or update
+# it if it doesn't.
+if [ ! -d "$SCRATCH_DIR/.git" ]; then
+  run_command 'git clone "$REPO_DIR" "$SCRATCH_DIR"'
+  
+  run_command 'cd "$SCRATCH_DIR"'
+  echo "$SCRATCH_DIR"
+  cd "$SCRATCH_DIR"
+else
+  run_command 'cd "$SCRATCH_DIR"'
+  echo "$SCRATCH_DIR"
+  cd "$SCRATCH_DIR"
+
+  run_command 'git pull origin main --rebase'
+fi
+
+# If there's a requirements.txt file in the repo, then this is a Python repo.
+# Set up a virtualenv.
+if [ -f "$SCRATCH_DIR/requirements.txt" ] || [ -f "$SCRATCH_DIR/dev_requirements.txt" ]; then
+  run_command 'uv venv --quiet --allow-existing .venv --python 3.14'
+  run_command 'source .venv/bin/activate'
+  source .venv/bin/activate  
+  run_pip_sync
+fi
+
+echo ""
+
+~/repos/scripts/ci/ts

ci/run_post_receive_checks.sh (0) → ci/run_post_receive_checks.sh (717)

diff --git a/ci/run_post_receive_checks.sh b/ci/run_post_receive_checks.sh
new file mode 100755
index 0000000..c3e0a63
--- /dev/null
+++ b/ci/run_post_receive_checks.sh
@@ -0,0 +1,32 @@
+#!/usr/bin/env bash
+
+if (( $# != 1 )); then
+  echo "Usage: $0 REPO_NAME" >&2
+  exit 1
+fi
+
+# REPO_NAME is the name of the repository to run tests for
+REPO_NAME="$1"
+
+print_purple() {
+    echo -e "\033[35m-> $@\033[0m"
+}
+
+NOW=$(date +'%Y-%m-%dT%H-%M-%S')
+LOG_NAME="$REPO_NAME.$NOW.log"
+LOG_PATH="$TMPDIR/$LOG_NAME"
+
+touch $LOG_PATH
+
+echo "Running tests"
+echo "Follow logs: tail -f \$TMPDIR/$LOG_NAME"
+
+bash ~/repos/scripts/ci/post_receive_tests.sh "$REPO_NAME" > $LOG_PATH 2>&1
+
+if (( $? == 0 )); then
+  print_success "Post-receive tests passed!"
+else
+  print_error "Post-receive tests failed!"
+  cp "$LOG_PATH" ~/Desktop/$REPO_NAME.$NOW.failed.log
+  print_error "See logs: cat ~/Desktop/$REPO_NAME.$NOW.failed.log"
+fi

ts (1741) → ci/ts (1741)

diff --git a/ts b/ci/ts
similarity index 100%
rename from ts
rename to ci/ts

config.fish (6653) → config.fish (6688)

diff --git a/config.fish b/config.fish
index ff78ae8..20a352c 100644
--- a/config.fish
+++ b/config.fish
@@ -48,6 +48,7 @@ end
 
 prepend_to_path ~/repos/scripts
 prepend_to_path ~/repos/scripts/aws
+prepend_to_path ~/repos/scripts/ci
 prepend_to_path ~/repos/scripts/debug
 prepend_to_path ~/repos/scripts/fs
 prepend_to_path ~/repos/scripts/git