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
38 changes: 38 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
name: Release

# Tag-driven releases: a release PR bumps src/version.h + devsoak.readme
# through normal review/CI, then pushing the matching v<version> tag runs
# this. verify-version confirms the pushed tag actually matches those two
# files; release then delegates the build -> GitHub release -> Aminet-
# upload-behind-required-review pipeline to sidick/amiga-workflows'
# aminet-release.yml. The $VER-embedded-in-binary check lives in the
# Makefile's own `dist` target.

on:
push:
tags: ['v*']

jobs:
verify-version:
name: Verify tag matches src/version.h / devsoak.readme
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- run: ./scripts/verify-version.sh "${{ github.ref_name }}"

release:
name: Build, release, and (on approval) publish to Aminet
needs: verify-version
# A reusable workflow's own `permissions:` can only narrow, never
# widen, what the calling job already has - aminet-release.yml's
# `gh release create` step needs contents: write, so it has to be
# granted here too, not just relied on inside that workflow.
permissions:
contents: write
uses: sidick/amiga-workflows/.github/workflows/aminet-release.yml@v1
with:
tag: ${{ github.ref_name }}
project-name: devsoak
lha-path: dist/devsoak.lha
readme-path: dist/devsoak.readme
category: dev/misc
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ devsoak
*.hdf
*.uaem
site/
dist/
25 changes: 25 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
BSD 2-Clause License

Copyright (c) 2026, Simon Dick

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.

2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
41 changes: 39 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ TARGET := devsoak
DOCKER_IMAGE := stefanreinauer/amiga-gcc:gcc-v16.1
HOST_CC := $(shell command -v m68k-amigaos-gcc 2>/dev/null)

.PHONY: all clean version devsoak-native
.PHONY: all clean version devsoak-native dist

ifeq ($(HOST_CC),)

Expand Down Expand Up @@ -52,4 +52,41 @@ $(OBJDIR):
mkdir -p $(OBJDIR)

clean:
rm -rf $(OBJDIR) $(TARGET)
rm -rf $(OBJDIR) $(TARGET) dist

# --- dist: assemble the Aminet upload pair (archive + .readme) --------------
#
# Homebrew's/Ubuntu's `lha` is Lhasa (extract-only) and the last real lha
# *release* tag no longer builds with modern compilers, so a pinned master
# commit is built from source into dist/tools/ instead. Override with a
# known-good archiver: `make dist LHA=/path/to/real/lha`.
LHA_REPO := https://github.com/jca02266/lha.git
LHA_COMMIT := 86094cb56aba34de45668f39f74fcfb61e9d7fb6
LHA ?= dist/tools/lha

dist/tools/lha:
@mkdir -p dist/tools
rm -rf dist/tools/lha-src
git clone -q $(LHA_REPO) dist/tools/lha-src
cd dist/tools/lha-src && \
git -c advice.detachedHead=false checkout -q $(LHA_COMMIT) && \
autoreconf -fi >/dev/null 2>&1 && ./configure >/dev/null && \
$(MAKE) >/dev/null
cp dist/tools/lha-src/src/lha dist/tools/lha
rm -rf dist/tools/lha-src

# Rebuilds the binary itself (the release workflow's dist job runs `make
# dist` standalone). The $VER grep confirms the binary just built embeds the
# CURRENT src/version.h DEVSOAK_VERSION; the release workflow's tag-vs-source
# check (scripts/verify-version.sh) separately confirms the tag matches
# src/version.h, closing the loop: tag == src/version.h == the binary.
dist: $(LHA)
rm -f $(TARGET)
$(MAKE) all
@v=$$(sed -n 's/^#define DEVSOAK_VERSION[[:space:]]*"\(.*\)"$$/\1/p' src/version.h); \
grep -aqF "\$$VER: devsoak $$v (" $(TARGET) || { echo "dist: $(TARGET) lacks \"\$$VER: devsoak $$v (...)\" - stale build/?"; exit 1; }
rm -rf dist/devsoak
mkdir -p dist/devsoak
cp $(TARGET) devsoak.quirks README.md LICENSE dist/devsoak/
cp devsoak.readme dist/
cd dist && $(abspath $(LHA)) aq devsoak.lha devsoak
29 changes: 29 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -467,3 +467,32 @@ partitions on the driver and run filesystem-level stress (e.g.
FileSystemStressTest from Aminet) while devsoak works a separate range of
the same unit — filesystems pick different command dialects and catch
"works with everything except PFS3" bugs.

## Releases

`src/version.h` is the single source of truth for the version: bump
`DEVSOAK_VERSION` and `DEVSOAK_VERSION_DATE` there and the matching
`Version:` field in `devsoak.readme`, land that as a normal reviewed PR,
then push a `v<version>` tag (e.g. `v0.1`) to `main`. That tag drives
`.github/workflows/release.yml`:

1. `scripts/verify-version.sh` refuses the release if the tag doesn't match
both `src/version.h` and `devsoak.readme`, or if the `$VER` date wasn't
bumped since the previous release.
2. `make dist` rebuilds the binary, greps it for the exact
`$VER: devsoak <version> (...)` string (a stale `obj/`/`devsoak` would
otherwise ship silently), and packs `devsoak`, `devsoak.quirks`,
`README.md` and `LICENSE` into `dist/devsoak.lha` alongside
`dist/devsoak.readme`.
3. A GitHub Release is published with the archive attached, then (behind a
required-reviewer gate on the `aminet` environment) the same archive is
uploaded to Aminet under `dev/misc`.

The `$VER` string (`$VER: devsoak <version> (<date>)`) is what AmigaOS's
`Version` shell command and utilities like VersionScanner or Aminet's own
tooling read; check it with `Version devsoak FULL` or, from a host build,
`strings devsoak | grep '\$VER'`.

## License

BSD 2-Clause. See [`LICENSE`](LICENSE).
59 changes: 59 additions & 0 deletions devsoak.readme
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
Short: Destructive soak/correctness tester for trackdisk drivers
Author: simond@irrelevant.org (Simon Dick)
Uploader: simond@irrelevant.org (Simon Dick)
Type: dev/misc
Version: 0.1
Architecture: m68k-amigaos >= 3.0; m68k-aros
Requires: 68000

devsoak is a destructive correctness and soak tester for trackdisk-style
AmigaOS block device drivers. One run answers two questions: does every
command the driver accepts do the right thing - including the edge cases
(bounds, alignment, zero length, the 64-bit offset high word, stale ETD
change counts, unsupported commands) - and does it keep doing the right
thing for hours while several tasks issue overlapping reads, writes and
housekeeping commands with multiple requests in flight.

devsoak works a fixed sector range so it can hold per-sector state and
prove *content* correctness under sustained concurrent load, not just
whether a driver accepts a command. It complements devtest
(https://github.com/cdhooper/amiga_devtest), which probes acceptance and
sweeps whole devices.

This release ships:

devsoak - the tester, a single static binary
devsoak.quirks - the seed quirks file (known driver-specific behaviour
to skip, expect, or pin rather than treat as a bug -
see README.md's Quirks section); devsoak looks for
this file beside its own binary

devsoak is destructive: it will overwrite the sector range you point it
at. Never run it against a range holding data you want to keep.

Requirements
------------
- AmigaOS 3.0+ (KS 1.3 is also supported at reduced dialect coverage)
or AROS m68k, 68000 or better.
- A block device to test, and a sector range on it you do not need.

Quick start
-----------
devsoak scsi.device 0 -d -r 0,8M -t 30s -y
devsoak lide.device 0 -d -r 0,64M -w 4 -q 4 -t 8h -A 15 -y

See https://sidick.github.io/devsoak/ for the full CLI reference, the
quirks-file format, and the fingerprinting workflow.

AI disclosure
-------------
devsoak is written largely by an AI coding agent (Anthropic's Claude /
Claude Code), under human direction, review and emulator/on-hardware
testing. Full source is open (BSD 2-Clause) for review.

Documentation, source and licence
---------------------------------
Free and open source, BSD 2-Clause. Full documentation (versioned per
release), source and issue tracker:
https://sidick.github.io/devsoak/
https://github.com/sidick/devsoak
40 changes: 40 additions & 0 deletions scripts/verify-version.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
#!/bin/sh
# Verifies a release tag (e.g. v0.1) matches src/version.h and devsoak.readme
# before release.yml hands off to sidick/amiga-workflows' aminet-release.yml
# - the calling repo's own version-file format is project-specific, so this
# check stays here rather than in the shared workflow (see that repo's own
# aminet-release.yml header comment for why).
#
# Usage: scripts/verify-version.sh vX.Y
set -eu

tag_ref="${1:?usage: verify-version.sh <tag, e.g. v0.1>}"
tag="${tag_ref#v}"

src=$(sed -n 's/^#define DEVSOAK_VERSION[[:space:]]*"\(.*\)"$/\1/p' src/version.h)
readme=$(sed -n 's/^Version:[[:space:]]*\(.*\)$/\1/p' devsoak.readme)
date=$(sed -n 's/^#define DEVSOAK_VERSION_DATE[[:space:]]*"\(.*\)"$/\1/p' src/version.h)

echo "tag=$tag src/version.h=$src ($date) devsoak.readme=$readme"
[ "$tag" = "$src" ] || { echo "::error file=src/version.h::Tag v$tag does not match DEVSOAK_VERSION \"$src\""; exit 1; }
[ "$tag" = "$readme" ] || { echo "::error file=devsoak.readme::Tag v$tag does not match Version: \"$readme\""; exit 1; }

case "$date" in
[0-9][0-9].[0-9][0-9].[0-9][0-9][0-9][0-9]) ;;
*) echo "::error file=src/version.h::DEVSOAK_VERSION_DATE \"$date\" is not DD.MM.YYYY"; exit 1 ;;
esac

# A release PR that bumps DEVSOAK_VERSION but forgets the date would
# otherwise ship a binary whose $VER string still carries the previous
# release's date. Compare against the previous release tag's own
# src/version.h (skipped gracefully if there isn't one, e.g. this is the
# first release).
prev_tag=$(git tag -l 'v*' --sort=-v:refname | grep -vFx "v$tag" | head -n1 || true)
if [ -n "$prev_tag" ]; then
prev_date=$(git show "$prev_tag:src/version.h" 2>/dev/null | \
sed -n 's/^#define DEVSOAK_VERSION_DATE[[:space:]]*"\(.*\)"$/\1/p')
if [ -n "$prev_date" ] && [ "$date" = "$prev_date" ]; then
echo "::error file=src/version.h::DEVSOAK_VERSION_DATE \"$date\" is unchanged since $prev_tag - bump it too"
exit 1
fi
fi
4 changes: 2 additions & 2 deletions src/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
*/

#include "devsoak.h"
#include "version.h"

#include <proto/exec.h>
#include <proto/dos.h>
Expand All @@ -15,8 +16,7 @@
struct Config cfg;
struct DevUnderTest dev;

static const char verstag[] __attribute__((used)) =
"$VER: devsoak 0.1 (01.09.2026)";
DEVSOAK_VERSTAG

/* args.c, deliberately not declared in devsoak.h (internal to this program) */
extern LONG args_parse(int argc, char **argv);
Expand Down
24 changes: 24 additions & 0 deletions src/version.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#ifndef DEVSOAK_VERSION_H
#define DEVSOAK_VERSION_H

/* Single source of truth for the release version.
*
* A release PR bumps DEVSOAK_VERSION and DEVSOAK_VERSION_DATE here and the
* matching `Version:` field in devsoak.readme; the tag-driven release
* workflow (.github/workflows/release.yml) refuses a v<tag> that does not
* match both.
*
* DEVSOAK_VERSION_DATE is the AmigaOS $VER date, <dd>.<mm>.<yyyy> per
* https://wiki.amigaos.net/wiki/Version_Strings */
#define DEVSOAK_VERSION "0.1"
#define DEVSOAK_VERSION_DATE "01.09.2026"

/* Embedded AmigaOS version string, findable by the shell `Version` command.
* The leading "\0" guards against an adjacent string in the binary running
* into ours; `used` keeps the otherwise-unreferenced constant out of the
* optimiser's reach. */
#define DEVSOAK_VERSTAG \
static const char verstag[] __attribute__((used)) = \
"\0$VER: devsoak " DEVSOAK_VERSION " (" DEVSOAK_VERSION_DATE ")";

#endif
Loading