프로젝트 소스 공개

This commit is contained in:
2026-09-06 17:11:40 +09:00
commit 0f0431de5a
676 changed files with 106118 additions and 0 deletions
+63
View File
@@ -0,0 +1,63 @@
import json
import os
import sys
import tempfile
import unittest
from pathlib import Path
from unittest import mock
TOOLS = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(TOOLS))
from release_configuration import ConfigurationError, load_configuration
class ReleaseConfigurationTests(unittest.TestCase):
def test_public_checkout_uses_local_build_paths_and_requires_deploy_target(self):
with tempfile.TemporaryDirectory() as temporary, mock.patch.dict(os.environ, {}, clear=True):
root = Path(temporary)
config = load_configuration(root)
self.assertEqual(config.stable_app, root / '_workspace/builds/Stable/ProjectQuint.app')
self.assertEqual(config.releases_root, root / '_workspace/releases')
with self.assertRaisesRegex(ConfigurationError, 'DEPLOY_HOST'):
config.require_deployment()
def test_environment_overrides_local_config_and_preserves_spaces(self):
with tempfile.TemporaryDirectory() as temporary:
root = Path(temporary)
key = root / 'test key'
key.touch()
(root / '.release.local.json').write_text(json.dumps({
'deploy_host': 'release@host.invalid',
'ssh_key': str(key),
'remote_source': '/srv/projectquint/site-source',
}))
with mock.patch.dict(os.environ, {'PROJECTQUINT_DEPLOY_HOST': 'release@override.invalid'}, clear=True):
config = load_configuration(root)
config.require_deployment()
self.assertEqual(config.deploy_host, 'release@override.invalid')
self.assertEqual(config.ssh_key, key)
self.assertEqual(config.remote_source, '/srv/projectquint/site-source')
def test_invalid_configuration_fails_before_deployment(self):
with tempfile.TemporaryDirectory() as temporary, mock.patch.dict(os.environ, {}, clear=True):
root = Path(temporary)
for text in ['{', '{"unexpected": "value"}', '{"ssh_key": "relative/key"}']:
with self.subTest(text=text):
(root / '.release.local.json').write_text(text)
with self.assertRaises(ConfigurationError):
load_configuration(root)
def test_shell_export_quotes_values_and_does_not_execute_them(self):
import subprocess
with tempfile.TemporaryDirectory() as temporary, mock.patch.dict(os.environ, {}, clear=True):
root = Path(temporary)
key = root / "key with spaces"
key.touch()
(root / '.release.local.json').write_text(json.dumps({
'deploy_host': 'release@host.invalid', 'ssh_key': str(key),
'remote_source': '/srv/projectquint/site-source',
}))
config = load_configuration(root)
exports = config.shell_exports()
output = subprocess.check_output(['bash', '-c', exports + '\nprintf "%s" "$PROJECTQUINT_DEPLOY_KEY"'], text=True)
self.assertEqual(output, str(key))
+154
View File
@@ -0,0 +1,154 @@
import importlib.util
import json
import tempfile
import unittest
import sys
from pathlib import Path
from unittest import mock
MODULE_PATH = Path(__file__).resolve().parents[1] / "release_mac_alpha.py"
sys.path.insert(0, str(MODULE_PATH.parent))
SPEC = importlib.util.spec_from_file_location("release_mac_alpha", MODULE_PATH)
release = importlib.util.module_from_spec(SPEC)
SPEC.loader.exec_module(release)
class ReleaseHarnessTests(unittest.TestCase):
def test_parse_revision_accepts_current_alpha_contract(self):
self.assertEqual(release.parse_revision_from_version("0.1.0-alpha.12"), 12)
with self.assertRaises(release.ReleaseError):
release.parse_revision_from_version("0.1.0")
def test_replace_exact_regex_fails_closed_on_ambiguous_input(self):
self.assertEqual(
release.replace_exact_regex(
"revision=2",
r"revision=\d+",
"revision=3",
label="revision",
),
"revision=3",
)
with self.assertRaises(release.ReleaseError):
release.replace_exact_regex(
"revision=2\nrevision=2",
r"revision=\d+",
"revision=3",
label="revision",
)
def test_set_release_version_updates_source_and_independent_contract_test(self):
with tempfile.TemporaryDirectory() as temporary:
root = Path(temporary)
source = root / "ProjectQuintReleaseVersion.cs"
tests = root / "ProjectQuintReleaseVersionTests.cs"
source.write_text(
"public const int ChannelRevision = 2;\n"
"public const int BuildNumber = 2;\n",
encoding="utf-8",
)
tests.write_text(
"Assert.That(ProjectQuintReleaseVersion.ChannelRevision, Is.EqualTo(2));\n"
"Assert.That(ProjectQuintReleaseVersion.BuildNumber, Is.EqualTo(2));\n"
'Assert.That(ProjectQuintReleaseVersion.DisplayVersion, Is.EqualTo("0.1.0-alpha.2"));\n'
'Is.EqualTo("0.1.0-alpha.2 (build 2)"));\n',
encoding="utf-8",
)
with mock.patch.object(release, "VERSION_SOURCE", source), mock.patch.object(
release, "VERSION_TEST", tests
):
release.set_release_version(3)
self.assertIn("ChannelRevision = 3", source.read_text())
self.assertIn("BuildNumber = 3", source.read_text())
self.assertIn("0.1.0-alpha.3", tests.read_text())
self.assertIn("build 3", tests.read_text())
def test_update_site_writes_download_manifest_and_one_changelog_entry(self):
with tempfile.TemporaryDirectory() as temporary:
root = Path(temporary)
download = root / "download.astro"
changelog = root / "changelog.astro"
manifest = root / "latest.json"
appcast = root / "appcast.xml"
readme = root / "README.md"
download.write_text(
'href="/downloads/0.1.0-alpha.2/ProjectQuint-0.1.0-alpha.2-macOS-universal.dmg"\n'
"<strong>0.1.0 ALPHA 2</strong>\n"
"<div><dt>VERSION</dt><dd>0.1.0-alpha.2</dd></div>\n"
"<div><dt>SIZE</dt><dd>77.7 MB</dd></div>\n"
f"<code>{'a' * 64}</code>\n",
encoding="utf-8",
)
changelog.write_text(' <section class="timeline">\n', encoding="utf-8")
manifest.write_text("{}\n", encoding="utf-8")
appcast.write_text("", encoding="utf-8")
readme.write_text("# Site\n", encoding="utf-8")
digest = "b" * 64
signature = "A" * 86 + "=="
patches = [
mock.patch.object(release, "DOWNLOAD_PAGE", download),
mock.patch.object(release, "CHANGELOG_PAGE", changelog),
mock.patch.object(release, "UPDATE_MANIFEST", manifest),
mock.patch.object(release, "SPARKLE_APPCAST", appcast),
mock.patch.object(release, "SITE_README", readme),
]
for patch in patches:
patch.start()
try:
release.update_site(
3,
digest,
80_000_000,
signature,
"Release title",
["Change one", "Change two"],
"2026-07-29T18:00:00+09:00",
)
release.update_site(
3,
digest,
80_000_000,
signature,
"Release title",
["Change one", "Change two"],
"2026-07-29T18:00:00+09:00",
)
finally:
for patch in reversed(patches):
patch.stop()
self.assertIn("0.1.0-alpha.3", download.read_text())
self.assertIn("80.0 MB", download.read_text())
self.assertEqual(changelog.read_text().count("0.1.0-ALPHA.3"), 1)
self.assertEqual(readme.read_text().count("## 0.1.0-alpha.3 release"), 1)
data = json.loads(manifest.read_text())
self.assertEqual(data["buildNumber"], 3)
self.assertEqual(data["sha256"], digest)
self.assertEqual(data["sizeBytes"], 80_000_000)
appcast_text = appcast.read_text()
self.assertIn("0.1.0-alpha.3", appcast_text)
self.assertIn('sparkle:version>3</sparkle:version', appcast_text)
self.assertIn('length="80000000"', appcast_text)
self.assertIn(signature, appcast_text)
def test_build_stable_does_not_block_for_a_running_existing_app(self):
with mock.patch.object(release, "run") as run:
app = release.build_stable()
self.assertEqual(app, release.STABLE_APP)
run.assert_called_once()
self.assertEqual(
run.call_args.args[0],
[release.ROOT / "Tools/build-mac-stable-release.sh"],
)
self.assertEqual(
run.call_args.kwargs["env"]["PROJECTQUINT_BUILD_PATH"],
str(release.STABLE_APP),
)
if __name__ == "__main__":
unittest.main()