171 lines
6.3 KiB
Python
171 lines
6.3 KiB
Python
import subprocess
|
|
import tempfile
|
|
import unittest
|
|
from pathlib import Path
|
|
|
|
import release_manager
|
|
|
|
|
|
class VersionTests(unittest.TestCase):
|
|
def test_bumps_each_semver_component(self):
|
|
self.assertEqual(release_manager.bump_semver("0.1.2", "patch"), "0.1.3")
|
|
self.assertEqual(release_manager.bump_semver("0.1.2", "minor"), "0.2.0")
|
|
self.assertEqual(release_manager.bump_semver("0.1.2", "major"), "1.0.0")
|
|
|
|
def test_android_code_is_monotonic(self):
|
|
self.assertEqual(release_manager.semantic_version_code("0.1.3"), 1003)
|
|
self.assertEqual(release_manager.suggest_version_code("0.1.3", 5000), 5001)
|
|
|
|
def test_rejects_non_release_semver(self):
|
|
with self.assertRaises(ValueError):
|
|
release_manager.parse_semver("1.2")
|
|
|
|
|
|
class SafetyTests(unittest.TestCase):
|
|
def test_safe_update_options_are_enabled_by_default(self):
|
|
self.assertTrue(release_manager.DEFAULT_GIT_TRUENAS_ONLY)
|
|
self.assertTrue(release_manager.DEFAULT_RUN_TESTS)
|
|
|
|
def test_detects_outputs_and_secrets(self):
|
|
paths = [
|
|
"src/game/example.ts",
|
|
"dist-android/assets/app.js",
|
|
"android/app/release.apk",
|
|
".env.production",
|
|
"FINE.key.ts",
|
|
"scripts/content/publish-content.mjs",
|
|
"public/data/healer-man-loot.sqlite",
|
|
]
|
|
self.assertEqual(
|
|
release_manager.find_dangerous_staged_paths(paths),
|
|
[
|
|
"dist-android/assets/app.js",
|
|
"android/app/release.apk",
|
|
".env.production",
|
|
],
|
|
)
|
|
|
|
def test_truenas_commands_pin_expected_commit(self):
|
|
block = release_manager.generate_truenas_deploy_commands("abc123")
|
|
self.assertIn("EXPECTED_COMMIT='abc123'", block)
|
|
self.assertIn("pull --ff-only", block)
|
|
self.assertIn('test "$ACTUAL_COMMIT" = "$EXPECTED_COMMIT"', block)
|
|
|
|
def test_full_release_publishes_verified_content_before_building(self):
|
|
checks = release_manager.full_release_checks("npm")
|
|
commands = [command for command, _label in checks]
|
|
publish_index = commands.index(["npm", "run", "content:publish"])
|
|
self.assertLess(publish_index, commands.index(["npm", "run", "build"]))
|
|
self.assertLess(publish_index, commands.index(["npm", "run", "android:sync"]))
|
|
|
|
def test_git_and_truenas_checks_skip_only_the_android_stage(self):
|
|
checks = release_manager.full_release_checks("npm", include_android=False)
|
|
commands = [command for command, _label in checks]
|
|
self.assertIn(["npm", "run", "content:publish"], commands)
|
|
self.assertIn(["npm", "run", "build"], commands)
|
|
self.assertNotIn(["npm", "run", "android:sync"], commands)
|
|
|
|
def test_disabled_test_suites_keep_safety_and_build_checks(self):
|
|
checks = release_manager.full_release_checks(
|
|
"npm",
|
|
include_android=False,
|
|
include_tests=False,
|
|
)
|
|
commands = [command for command, _label in checks]
|
|
self.assertEqual(
|
|
commands,
|
|
[
|
|
["npm", "run", "content:publish"],
|
|
["npm", "run", "build"],
|
|
],
|
|
)
|
|
|
|
def test_only_test_failures_may_be_overridden(self):
|
|
checks = release_manager.full_release_checks("npm")
|
|
overridable = {
|
|
label for _command, label in checks
|
|
if release_manager.can_override_release_check(label)
|
|
}
|
|
self.assertEqual(
|
|
overridable,
|
|
{
|
|
"Content tests",
|
|
"Server tests",
|
|
"Application tests",
|
|
"Shelved-mode application tests",
|
|
},
|
|
)
|
|
self.assertFalse(release_manager.can_override_release_check("Production build"))
|
|
self.assertFalse(release_manager.can_override_release_check("Push main"))
|
|
|
|
def test_failed_test_warning_includes_command_and_specific_output(self):
|
|
failure = release_manager.ReleaseCommandError(
|
|
"Application tests",
|
|
["npm", "test"],
|
|
1,
|
|
"\x1b[31mFAIL\x1b[39m src/game/example.test.ts\n"
|
|
"Error: expected 2 to be 3",
|
|
)
|
|
warning = release_manager.format_failed_test_warning(failure)
|
|
self.assertIn("Application tests failed with exit code 1", warning)
|
|
self.assertIn("npm test", warning)
|
|
self.assertIn("FAIL src/game/example.test.ts", warning)
|
|
self.assertIn("Error: expected 2 to be 3", warning)
|
|
self.assertNotIn("\x1b", warning)
|
|
|
|
|
|
class ReleaseCandidateTests(unittest.TestCase):
|
|
def test_checks_untracked_files_without_changing_the_real_index(self):
|
|
with tempfile.TemporaryDirectory() as temp_dir:
|
|
project_root = Path(temp_dir)
|
|
|
|
def git(*args):
|
|
subprocess.run(
|
|
["git", *args],
|
|
cwd=project_root,
|
|
stdout=subprocess.PIPE,
|
|
stderr=subprocess.PIPE,
|
|
check=True,
|
|
)
|
|
|
|
git("init", "--quiet")
|
|
(project_root / "tracked.ts").write_text("export const tracked = true;\n")
|
|
git("add", "tracked.ts")
|
|
git(
|
|
"-c",
|
|
"user.name=Healer Man Tests",
|
|
"-c",
|
|
"user.email=tests@example.invalid",
|
|
"commit",
|
|
"--quiet",
|
|
"-m",
|
|
"Initial commit",
|
|
)
|
|
|
|
untracked = project_root / "untracked.ts"
|
|
untracked.write_text("export const untracked = true;\n\n")
|
|
with self.assertRaisesRegex(
|
|
release_manager.ReleaseError, "untracked.ts.*new blank line at EOF"
|
|
):
|
|
release_manager.validate_release_candidates(project_root)
|
|
|
|
untracked.write_text("export const untracked = true;\n")
|
|
self.assertEqual(
|
|
release_manager.validate_release_candidates(project_root),
|
|
["untracked.ts"],
|
|
)
|
|
self.assertEqual(
|
|
subprocess.run(
|
|
["git", "diff", "--cached", "--name-only"],
|
|
cwd=project_root,
|
|
text=True,
|
|
stdout=subprocess.PIPE,
|
|
check=True,
|
|
).stdout,
|
|
"",
|
|
)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|