From da33b5f9e409b35429b31ab6b76e98787ef65ac0 Mon Sep 17 00:00:00 2001 From: helly25 <6420169+helly25@users.noreply.github.com> Date: Sat, 17 Oct 2026 22:00:47 +0100 Subject: [PATCH 1/4] Test bashtest paths correctly as an external module --- bashtest/tests/BUILD.bazel | 1 + bashtest/tests/external_consumer_test.sh | 4 +++- bashtest/tests/tools_test.sh | 5 +++-- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/bashtest/tests/BUILD.bazel b/bashtest/tests/BUILD.bazel index 60a633a..da29498 100644 --- a/bashtest/tests/BUILD.bazel +++ b/bashtest/tests/BUILD.bazel @@ -93,6 +93,7 @@ bashtest( env = { "TARGET_DATA": "$(rlocationpath :target_data)", "TARGET_LOCATION": "$(location :target_data)", + "TARGET_ROOTPATH": "$(rootpath :target_data)", }, tools = { "HELPER": ":helper", diff --git a/bashtest/tests/external_consumer_test.sh b/bashtest/tests/external_consumer_test.sh index ba01b12..6d43f05 100755 --- a/bashtest/tests/external_consumer_test.sh +++ b/bashtest/tests/external_consumer_test.sh @@ -61,7 +61,9 @@ test::consumer() { } test_runner EOF -bazel test --enable_runfiles=no --test_output=errors //:consumer_test +# Also run the framework's own suite as an external dependency, as BCR does. +# A standalone consumer test alone cannot catch root-workspace assumptions in it. +bazel test --enable_runfiles=no --test_output=errors //:consumer_test @framework//bashtest/tests/... bazel run --enable_runfiles=no //:consumer_test -- --keep-tmpdir=never # Exercise both documented update spellings with a workspace path containing spaces. diff --git a/bashtest/tests/tools_test.sh b/bashtest/tests/tools_test.sh index 877bf63..3fa4bc1 100755 --- a/bashtest/tests/tools_test.sh +++ b/bashtest/tests/tools_test.sh @@ -28,8 +28,9 @@ test::executable_and_runfiles() { # shellcheck disable=SC2154 expect_eq "${HELPER}" "${HELPER_ALIAS}" # shellcheck disable=SC2154 - # location retains sh_test rootpath semantics; use rlocation above for I/O. - expect_eq 'bashtest/tests/target_data.txt' "${TARGET_LOCATION}" + # location retains sh_test rootpath semantics in root and external repos. + # Use rlocation above for I/O, including manifest-only execution. + expect_eq "${TARGET_ROOTPATH}" "${TARGET_LOCATION}" # Distinct executables must both be available in this single test invocation. # shellcheck disable=SC2154 expect_ne "${HELPER}" "${SECOND_HELPER}" From 4e7d226a4505d008c644cb8532b61ba426347092 Mon Sep 17 00:00:00 2001 From: helly25 <6420169+helly25@users.noreply.github.com> Date: Sat, 17 Oct 2026 22:03:03 +0100 Subject: [PATCH 2/4] Advance development version to 0.7.1 after release --- CHANGELOG.md | 6 ++++++ MODULE.bazel | 2 +- bashtest/tests/external_consumer_test.sh | 2 +- bashtest/tests/tools_consumer_test.sh | 2 +- 4 files changed, 9 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 509e6c9..cb2a422 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +# 0.7.1 + +* Fixed the tools test's path expectation when bashtest is an external module. +* Run the framework test suite as an external dependency in CI to catch + root-workspace assumptions before BCR publication. + # 0.7.0 * Automatically provision Bash and Unix utilities, including PCRE support, for diff --git a/MODULE.bazel b/MODULE.bazel index 2b05a8b..04f8bd8 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -17,7 +17,7 @@ module( name = "mboworks_bashtest", - version = "0.7.0", + version = "0.7.1", ) bazel_dep(name = "bazel_skylib", version = "1.9.2") diff --git a/bashtest/tests/external_consumer_test.sh b/bashtest/tests/external_consumer_test.sh index 6d43f05..cfb56b8 100755 --- a/bashtest/tests/external_consumer_test.sh +++ b/bashtest/tests/external_consumer_test.sh @@ -34,7 +34,7 @@ cd "${root}/consumer with spaces" cp "${repo}/.bazelversion" .bazelversion cat > MODULE.bazel < BUILD.bazel <<'EOF' diff --git a/bashtest/tests/tools_consumer_test.sh b/bashtest/tests/tools_consumer_test.sh index 76d104f..517af91 100755 --- a/bashtest/tests/tools_consumer_test.sh +++ b/bashtest/tests/tools_consumer_test.sh @@ -34,7 +34,7 @@ cp "${repo}/.bazelversion" .bazelversion cp "${repo}/bashtest/tests/tool_fixture.bzl" tool_fixture.bzl cat > MODULE.bazel < Date: Sat, 17 Oct 2026 22:10:00 +0100 Subject: [PATCH 3/4] Check all development version references and test release transitions --- .pre-commit-config.yaml | 7 +-- .pre-commit/check_version.sh | 9 ++++ CHANGELOG.md | 3 ++ README.md | 6 ++- docs/infrastructure.md | 14 +++++ tools/version_test.py | 99 ++++++++++++++++++++++++++++++++++++ 6 files changed, 133 insertions(+), 5 deletions(-) create mode 100644 tools/version_test.py diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c33d73c..3f89c91 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -37,7 +37,7 @@ repos: name: Test publishing and CI policy language: system entry: python3 -m unittest discover -s tools -p '*_test.py' - files: ^(tools/|docs/assets/|release-site\.json$|\.github/workflows/) + files: ^(\.pre-commit/check_version\.sh$|tools/|docs/assets/|release-site\.json$|\.github/workflows/) pass_filenames: false - id: uncomment-bazelmod-includes name: uncomment-bazelmod-includes @@ -49,8 +49,9 @@ repos: - id: compare-versions name: compare-versions description: | - Require matching CHANGELOG.md and MODULE.bazel versions. After a release, - the first later commit must advance both beyond the latest release tag. + Require matching MODULE.bazel, CHANGELOG.md, README.md, and consumer fixture + versions. After a release, the first later commit must advance the development + version beyond the latest release tag. language: system entry: .pre-commit/check_version.sh pass_filenames: False diff --git a/.pre-commit/check_version.sh b/.pre-commit/check_version.sh index 53455ad..c5bf2f3 100755 --- a/.pre-commit/check_version.sh +++ b/.pre-commit/check_version.sh @@ -35,6 +35,15 @@ if [[ ! "${BAZELMOD_VERSION}" =~ ^(0|[1-9][0-9]*)[.](0|[1-9][0-9]*)[.](0|[1-9][0 die "MODULE.bazel version (${BAZELMOD_VERSION}) must use numeric X.Y.Z format." fi +# These examples and consumer fixtures describe this checkout's module version. +# Historical changelog entries and release templates intentionally do not. +for file in README.md bashtest/tests/external_consumer_test.sh bashtest/tests/tools_consumer_test.sh; do + reference_version="$(sed -nE 's/.*bazel_dep\(name = "mboworks_bashtest", version = "([^"]+)".*/\1/p' "${file}")" + if [[ "${reference_version}" != "${BAZELMOD_VERSION}" ]]; then + die "${file} bashtest dependency version (${reference_version}) != MODULE.bazel (${BAZELMOD_VERSION})." + fi +done + LATEST_RELEASE="$( git tag --list | awk '/^(0|[1-9][0-9]*)[.](0|[1-9][0-9]*)[.](0|[1-9][0-9]*)$/' | diff --git a/CHANGELOG.md b/CHANGELOG.md index cb2a422..63039b2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ # 0.7.1 +* Check that the README and consumer fixtures match the module version, with + regression tests for version consistency and post-release version bumps. + * Fixed the tools test's path expectation when bashtest is an external module. * Run the framework test suite as an external dependency in CI to catch root-workspace assumptions before BCR publication. diff --git a/README.md b/README.md index 8f03147..546b5a1 100644 --- a/README.md +++ b/README.md @@ -119,10 +119,12 @@ remains byte-for-byte, so CRLF and LF golden files are intentionally different. ### MODULE.bazel -Check [Releases](https://github.com/mboworks/bashtest/releases) for details. All that is needed is a `bazel_dep` instruction with the correct version. +The example tracks this checkout’s version, which may be unreleased on `main`. +Use a published version from [Releases](https://github.com/mboworks/bashtest/releases) +when consuming bashtest through the Bazel Central Registry. ``` -bazel_dep(name = "mboworks_bashtest", version = "0.6.1") +bazel_dep(name = "mboworks_bashtest", version = "0.7.1") ``` Executable helpers can be declared with `tools = {"GENERATOR": "//tools:generator"}`. diff --git a/docs/infrastructure.md b/docs/infrastructure.md index c29fb21..5c15933 100644 --- a/docs/infrastructure.md +++ b/docs/infrastructure.md @@ -62,3 +62,17 @@ style guide or competing shell formatter is introduced. Run `bazel test //...`, `python3 -m unittest discover -s tools -p '*_test.py'`, and `pre-commit run --all-files`. CI also builds the configured release documentation to validate links and mappings before publication. + +## Version consistency + +`MODULE.bazel` identifies the checkout's development version. The `compare-versions` +pre-commit hook checks it against the first `CHANGELOG.md` heading, the README's +Bazel dependency example, and both external consumer fixtures. Update all five +references together. Historical changelog entries retain their original versions; +release assets and BCR templates derive their versions during publication. + +The released commit may keep its release version. The first subsequent commit must +advance beyond the latest numeric release tag. `tools/version_test.py` exercises +mismatched and missing references, invalid versions, numeric tag ordering, and this +post-release transition in temporary Git repositories. These tests run through the +infrastructure pre-commit hook and CI. diff --git a/tools/version_test.py b/tools/version_test.py new file mode 100644 index 0000000..55611d0 --- /dev/null +++ b/tools/version_test.py @@ -0,0 +1,99 @@ +# SPDX-FileCopyrightText: Copyright (c) M. Boerger, the MBO Works authors +# SPDX-License-Identifier: Apache-2.0 +"""Exercise the version gate against isolated repository histories.""" + +from pathlib import Path +import subprocess +import tempfile +import unittest + + +ROOT = Path(__file__).resolve().parents[1] +REFERENCES = ( + 'README.md', + 'bashtest/tests/external_consumer_test.sh', + 'bashtest/tests/tools_consumer_test.sh', +) + + +class VersionTest(unittest.TestCase): + def setUp(self): + temporary = tempfile.TemporaryDirectory() + self.addCleanup(temporary.cleanup) + self.root = Path(temporary.name) + self.git('init', '--quiet') + self.git('config', 'user.name', 'Version test') + self.git('config', 'user.email', 'version-test@example.invalid') + self.git('config', 'commit.gpgsign', 'false') + self.set_version('0.7.0') + self.commit() + + def git(self, *args): + return subprocess.run(['git', *args], cwd=self.root, check=True, + capture_output=True, text=True) + + def commit(self): + self.git('add', '.') + self.git('-c', 'core.hooksPath=/dev/null', 'commit', '--quiet', + '--allow-empty', '-m', 'Fixture') + + def set_version(self, version): + (self.root / 'MODULE.bazel').write_text( + f'module(name = "mboworks_bashtest", version = "{version}")\n') + (self.root / 'CHANGELOG.md').write_text(f'# {version}\n\n# 0.1.0\n') + for name in REFERENCES: + path = self.root / name + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text(f'bazel_dep(name = "mboworks_bashtest", version = "{version}")\n') + + def check(self, error=None): + result = subprocess.run(['bash', str(ROOT / '.pre-commit/check_version.sh')], + cwd=self.root, capture_output=True, text=True) + if error is None: + self.assertEqual(result.returncode, 0, result.stderr) + else: + self.assertNotEqual(result.returncode, 0) + self.assertIn(error, result.stderr) + + def test_matching_versions_without_tags(self): + self.check() + + def test_each_reference_must_match(self): + for name in ('CHANGELOG.md', *REFERENCES): + with self.subTest(name=name): + self.set_version('0.7.0') + path = self.root / name + path.write_text(path.read_text().replace('0.7.0', '0.6.1')) + self.check(name) + + def test_missing_dependency_is_rejected(self): + for name in REFERENCES: + with self.subTest(name=name): + self.set_version('0.7.0') + (self.root / name).write_text('No dependency example\n') + self.check(name) + + def test_invalid_version_is_rejected(self): + self.set_version('0.7.0-rc1') + self.check('numeric X.Y.Z') + + def test_released_commit_then_next_development_commit(self): + self.git('tag', '0.7.0') + self.check() + self.commit() + self.check('HEAD contains later work') + self.set_version('0.7.1') + self.check() + + def test_latest_release_uses_numeric_order(self): + self.git('tag', '0.9.0') + self.git('tag', '0.10.0') + self.git('tag', 'unrelated-tag') + self.set_version('0.9.1') + self.check('older than latest release tag (0.10.0)') + self.set_version('0.10.1') + self.check() + + +if __name__ == '__main__': + unittest.main() From 539ac745d6b4d2bcfafb5b1d8027fb2540820ef8 Mon Sep 17 00:00:00 2001 From: helly25 <6420169+helly25@users.noreply.github.com> Date: Sat, 17 Oct 2026 22:16:09 +0100 Subject: [PATCH 4/4] Derive consumer versions and prepare release references before tagging --- .github/workflows/release_prep.sh | 4 +- .pre-commit-config.yaml | 6 +- .pre-commit/check_version.sh | 23 +++-- CHANGELOG.md | 5 +- README.md | 8 +- bashtest/tests/external_consumer_test.sh | 4 +- bashtest/tests/tools_consumer_test.sh | 4 +- docs/infrastructure.md | 33 ++++--- tools/prepare_release.py | 46 +++++++++ tools/trigger_release.sh | 5 +- tools/version_test.py | 120 ++++++++++++++++++----- 11 files changed, 201 insertions(+), 57 deletions(-) create mode 100644 tools/prepare_release.py diff --git a/.github/workflows/release_prep.sh b/.github/workflows/release_prep.sh index 2a73dbf..7566672 100755 --- a/.github/workflows/release_prep.sh +++ b/.github/workflows/release_prep.sh @@ -52,8 +52,8 @@ if [[ "${CHANGELOG_VERSION}" != "${VERSION}" ]]; then die "Tag = '${TAG}' does not match version = '${CHANGELOG_VERSION}' in CHANGELOG.md." fi -# Instead of embed the version in MODULE.bazel, we expect it to be correct already. -# perl -pi -e "s/version = \"\d+\.\d+\.\d+\",/version = \"${TAG}\",/g" MODULE.bazel +# Reject an unprepared tag rather than publishing inconsistent version references. +python3 tools/prepare_release.py --check "${VERSION}" # Empty `BUILD.bazel` { diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 3f89c91..59b9b76 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -49,9 +49,9 @@ repos: - id: compare-versions name: compare-versions description: | - Require matching MODULE.bazel, CHANGELOG.md, README.md, and consumer fixture - versions. After a release, the first later commit must advance the development - version beyond the latest release tag. + Require matching MODULE.bazel and CHANGELOG.md development versions, and a + released or release-prepared README example. After a release, the first later + commit must advance the development version beyond the latest release tag. language: system entry: .pre-commit/check_version.sh pass_filenames: False diff --git a/.pre-commit/check_version.sh b/.pre-commit/check_version.sh index c5bf2f3..d34d363 100755 --- a/.pre-commit/check_version.sh +++ b/.pre-commit/check_version.sh @@ -35,15 +35,6 @@ if [[ ! "${BAZELMOD_VERSION}" =~ ^(0|[1-9][0-9]*)[.](0|[1-9][0-9]*)[.](0|[1-9][0 die "MODULE.bazel version (${BAZELMOD_VERSION}) must use numeric X.Y.Z format." fi -# These examples and consumer fixtures describe this checkout's module version. -# Historical changelog entries and release templates intentionally do not. -for file in README.md bashtest/tests/external_consumer_test.sh bashtest/tests/tools_consumer_test.sh; do - reference_version="$(sed -nE 's/.*bazel_dep\(name = "mboworks_bashtest", version = "([^"]+)".*/\1/p' "${file}")" - if [[ "${reference_version}" != "${BAZELMOD_VERSION}" ]]; then - die "${file} bashtest dependency version (${reference_version}) != MODULE.bazel (${BAZELMOD_VERSION})." - fi -done - LATEST_RELEASE="$( git tag --list | awk '/^(0|[1-9][0-9]*)[.](0|[1-9][0-9]*)[.](0|[1-9][0-9]*)$/' | @@ -67,3 +58,17 @@ if [[ -n "${LATEST_RELEASE}" ]] && [[ "$(git rev-parse HEAD)" != "$(git rev-list -n1 "${LATEST_RELEASE}")" ]]; then die "Development version (${BAZELMOD_VERSION}) still matches latest release tag (${LATEST_RELEASE}), but HEAD contains later work. Bump MODULE.bazel and CHANGELOG.md." fi + +README_VERSION="$(sed -nE 's/.*bazel_dep\(name = "mboworks_bashtest", version = "([^"]+)".*/\1/p' README.md)" +if [[ ! "${README_VERSION}" =~ ^(0|[1-9][0-9]*)[.](0|[1-9][0-9]*)[.](0|[1-9][0-9]*)$ ]]; then + die "README.md must contain one numeric bashtest dependency version." +fi +# Development keeps the latest released installation example. Release preparation +# advances it to the module version in the reviewed commit that will be tagged. +if [[ -n "${LATEST_RELEASE}" ]]; then + if [[ "${README_VERSION}" != "${LATEST_RELEASE}" && "${README_VERSION}" != "${BAZELMOD_VERSION}" ]]; then + die "README.md (${README_VERSION}) must match latest release (${LATEST_RELEASE}) or prepared release (${BAZELMOD_VERSION})." + fi +elif [[ "$(version_key "${README_VERSION}")" > "$(version_key "${BAZELMOD_VERSION}")" ]]; then + die "README.md (${README_VERSION}) is newer than MODULE.bazel (${BAZELMOD_VERSION})." +fi diff --git a/CHANGELOG.md b/CHANGELOG.md index 63039b2..f7818b3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,8 @@ # 0.7.1 -* Check that the README and consumer fixtures match the module version, with - regression tests for version consistency and post-release version bumps. +* Derive consumer fixture versions from the module and automate the README + version update during release preparation. Test development/release version + consistency and post-release version bumps. * Fixed the tools test's path expectation when bashtest is an external module. * Run the framework test suite as an external dependency in CI to catch diff --git a/README.md b/README.md index 546b5a1..ed00228 100644 --- a/README.md +++ b/README.md @@ -119,12 +119,12 @@ remains byte-for-byte, so CRLF and LF golden files are intentionally different. ### MODULE.bazel -The example tracks this checkout’s version, which may be unreleased on `main`. -Use a published version from [Releases](https://github.com/mboworks/bashtest/releases) -when consuming bashtest through the Bazel Central Registry. +Use the latest published version from +[Releases](https://github.com/mboworks/bashtest/releases) when consuming bashtest +through the Bazel Central Registry. ``` -bazel_dep(name = "mboworks_bashtest", version = "0.7.1") +bazel_dep(name = "mboworks_bashtest", version = "0.7.0") ``` Executable helpers can be declared with `tools = {"GENERATOR": "//tools:generator"}`. diff --git a/bashtest/tests/external_consumer_test.sh b/bashtest/tests/external_consumer_test.sh index cfb56b8..07c816b 100755 --- a/bashtest/tests/external_consumer_test.sh +++ b/bashtest/tests/external_consumer_test.sh @@ -18,6 +18,8 @@ # Run outside a Bazel test: this creates and builds a separate consumer module. set -euo pipefail repo="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" +# The override consumes this checkout, including unreleased development versions. +version="$(sed -nE 's/.*version = "([^"]+)".*/\1/p' "${repo}/MODULE.bazel" | head -n1)" root="$(mktemp -d)" cleanup() { # Windows cannot remove a directory while it is a process working directory. @@ -34,7 +36,7 @@ cd "${root}/consumer with spaces" cp "${repo}/.bazelversion" .bazelversion cat > MODULE.bazel < BUILD.bazel <<'EOF' diff --git a/bashtest/tests/tools_consumer_test.sh b/bashtest/tests/tools_consumer_test.sh index 517af91..3379792 100755 --- a/bashtest/tests/tools_consumer_test.sh +++ b/bashtest/tests/tools_consumer_test.sh @@ -18,6 +18,8 @@ # Integration test: distinct default and test execution platforms, external module. set -euo pipefail repo="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" +# The override consumes this checkout, including unreleased development versions. +version="$(sed -nE 's/.*version = "([^"]+)".*/\1/p' "${repo}/MODULE.bazel" | head -n1)" root="$(mktemp -d)" cleanup() { bazel shutdown >/dev/null 2>&1 || true @@ -34,7 +36,7 @@ cp "${repo}/.bazelversion" .bazelversion cp "${repo}/bashtest/tests/tool_fixture.bzl" tool_fixture.bzl cat > MODULE.bazel <" -for tool in gh git gpg; do +for tool in gh git gpg python3; do command -v "${tool}" >/dev/null 2>&1 || die "Required tool '${tool}' is not installed." done @@ -40,6 +40,9 @@ CHANGELOG_VERSION="$(sed -rne 's,^# ([0-9]+([.][0-9]+)+.*)$,\1,p' CHANGELOG.md | [[ -z "$(git tag --list "${VERSION}")" ]] || die "Tag '${VERSION}' already exists." gh release view "${VERSION}" >/dev/null 2>&1 && die "Release '${VERSION}' already exists." +# All tracked release references must already be reviewed and committed. +python3 tools/prepare_release.py --check "${VERSION}" + if [[ "${DRY_RUN}" == true ]]; then echo "[dry-run] Would create and push signed tag '${VERSION}' at $(git rev-parse HEAD)." exit 0 diff --git a/tools/version_test.py b/tools/version_test.py index 55611d0..5338820 100644 --- a/tools/version_test.py +++ b/tools/version_test.py @@ -3,17 +3,15 @@ """Exercise the version gate against isolated repository histories.""" from pathlib import Path +import os +import shutil import subprocess import tempfile +import tarfile import unittest ROOT = Path(__file__).resolve().parents[1] -REFERENCES = ( - 'README.md', - 'bashtest/tests/external_consumer_test.sh', - 'bashtest/tests/tools_consumer_test.sh', -) class VersionTest(unittest.TestCase): @@ -41,10 +39,17 @@ def set_version(self, version): (self.root / 'MODULE.bazel').write_text( f'module(name = "mboworks_bashtest", version = "{version}")\n') (self.root / 'CHANGELOG.md').write_text(f'# {version}\n\n# 0.1.0\n') - for name in REFERENCES: - path = self.root / name - path.parent.mkdir(parents=True, exist_ok=True) - path.write_text(f'bazel_dep(name = "mboworks_bashtest", version = "{version}")\n') + self.set_readme(version) + + def set_readme(self, version): + (self.root / 'README.md').write_text( + f'bazel_dep(name = "mboworks_bashtest", version = "{version}")\n') + + def prepare(self, version, check=False): + args = ['python3', str(ROOT / 'tools/prepare_release.py'), version] + if check: + args.append('--check') + return subprocess.run(args, cwd=self.root, capture_output=True, text=True) def check(self, error=None): result = subprocess.run(['bash', str(ROOT / '.pre-commit/check_version.sh')], @@ -58,20 +63,89 @@ def check(self, error=None): def test_matching_versions_without_tags(self): self.check() - def test_each_reference_must_match(self): - for name in ('CHANGELOG.md', *REFERENCES): - with self.subTest(name=name): - self.set_version('0.7.0') - path = self.root / name - path.write_text(path.read_text().replace('0.7.0', '0.6.1')) - self.check(name) - - def test_missing_dependency_is_rejected(self): - for name in REFERENCES: - with self.subTest(name=name): - self.set_version('0.7.0') - (self.root / name).write_text('No dependency example\n') - self.check(name) + def test_changelog_must_match(self): + (self.root / 'CHANGELOG.md').write_text('# 0.6.1\n') + self.check('CHANGELOG.md') + + def test_missing_or_ambiguous_dependency_is_rejected(self): + path = self.root / 'README.md' + for content in ('No dependency example\n', path.read_text() * 2): + with self.subTest(content=content): + path.write_text(content) + self.check('README.md') + self.assertNotEqual(self.prepare('0.7.0').returncode, 0) + + def test_development_keeps_released_readme(self): + self.git('tag', '0.7.0') + self.set_version('0.7.1') + self.set_readme('0.7.0') + self.commit() + self.check() + self.set_readme('0.6.1') + self.check('README.md') + self.set_readme('0.7.2') + self.check('README.md') + + def test_release_preparation_and_next_development_cycle(self): + self.git('tag', '0.7.0') + self.set_version('0.7.1') + self.set_readme('0.7.0') + before = (self.root / 'README.md').read_text() + result = self.prepare('0.7.1', check=True) + self.assertNotEqual(result.returncode, 0) + self.assertIn('before tagging', result.stderr) + self.assertEqual((self.root / 'README.md').read_text(), before) + result = self.prepare('0.7.1') + self.assertEqual(result.returncode, 0, result.stderr) + self.assertIn('version = "0.7.1"', (self.root / 'README.md').read_text()) + self.assertEqual(self.prepare('0.7.1', check=True).returncode, 0) + self.assertEqual(self.prepare('0.7.1').returncode, 0) + self.check() + self.commit() + self.git('tag', '0.7.1') + self.check() + self.set_version('0.7.2') + self.set_readme('0.7.1') + self.commit() + self.check() + + def test_preparation_rejects_wrong_version_without_writing(self): + before = (self.root / 'README.md').read_text() + for version in ('0.7.1', '0.7.0-rc1'): + self.assertNotEqual(self.prepare(version).returncode, 0) + self.assertEqual((self.root / 'README.md').read_text(), before) + (self.root / 'CHANGELOG.md').write_text('# 0.6.1\n') + self.assertNotEqual(self.prepare('0.7.0').returncode, 0) + self.assertEqual((self.root / 'README.md').read_text(), before) + + def test_archive_rejects_unprepared_readme_and_packages_prepared_version(self): + for name in ('tools/prepare_release.py', 'tools/header.txt', + 'tools/release_notes.sh', '.github/release-notes.md.template'): + destination = self.root / name + destination.parent.mkdir(parents=True, exist_ok=True) + shutil.copyfile(ROOT / name, destination) + self.set_version('0.7.1') + self.set_readme('0.7.0') + self.commit() + command = ['bash', str(ROOT / '.github/workflows/release_prep.sh')] + environment = dict(os.environ, GITHUB_REF_NAME='0.7.1') + result = subprocess.run(command, cwd=self.root, env=environment, + capture_output=True, text=True) + self.assertNotEqual(result.returncode, 0) + self.assertIn('before tagging', result.stderr) + archive = self.root / 'bashtest-0.7.1.tar.gz' + self.assertFalse(archive.exists()) + self.assertEqual(self.prepare('0.7.1').returncode, 0) + self.commit() + result = subprocess.run(command, cwd=self.root, env=environment, + capture_output=True, text=True) + self.assertEqual(result.returncode, 0, result.stderr) + self.assertIn('version = "0.7.1"', result.stdout) + with tarfile.open(archive) as packaged: + for name in ('README.md', 'MODULE.bazel'): + content = packaged.extractfile('bashtest-0.7.1/' + name).read().decode() + self.assertIn('version = "0.7.1"', content) + self.assertEqual(packaged.extractfile('bashtest-0.7.1/VERSION').read(), b'0.7.1') def test_invalid_version_is_rejected(self): self.set_version('0.7.0-rc1')