blob: 63711eca084055350ec9f2700ba446e92d3f01af [file]
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
import importlib.util
import subprocess
import sys
import tempfile
import unittest
from pathlib import Path
ROOT = Path(__file__).resolve().parents[2]
MODULE_PATH = ROOT / "packaging/scripts/render_homebrew_formula.py"
TEMPLATE_PATH = ROOT / "packaging/homebrew/tsfile.rb.in"
VALUES = {
"SOURCE_REPOSITORY": "apache/tsfile",
"GIT_SHA": "abcdef1234567890abcdef1234567890abcdef12",
"HOMEBREW_VERSION": "2.5.0",
"SOURCE_SHA256": "0123456789abcdef" * 4,
}
TEMPLATE = """class Tsfile < Formula
url "https://github.com/@SOURCE_REPOSITORY@/archive/@GIT_SHA@.tar.gz"
version "@HOMEBREW_VERSION@"
sha256 "@SOURCE_SHA256@"
@BOTTLE_BLOCK@
end
"""
BOTTLE_BLOCK = """ bottle do
root_url "https://packages.apache.org/artifactory/tsfile/homebrew/release/versions/2.5.0/bottles"
sha256 cellar: :any, arm64_sequoia: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
sha256 cellar: :any, sequoia: "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
end"""
class RenderHomebrewFormulaTest(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.module = None
if MODULE_PATH.is_file():
spec = importlib.util.spec_from_file_location(
"render_homebrew_formula", MODULE_PATH
)
cls.module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(cls.module)
def renderer(self):
self.assertIsNotNone(self.module, "Homebrew Formula renderer must exist")
return self.module.render_formula
def test_renders_exact_immutable_source_and_both_bottle_tags(self):
rendered = self.renderer()(TEMPLATE, VALUES, BOTTLE_BLOCK)
self.assertEqual(
rendered,
"""class Tsfile < Formula
url "https://github.com/apache/tsfile/archive/abcdef1234567890abcdef1234567890abcdef12.tar.gz"
version "2.5.0"
sha256 "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
""" + BOTTLE_BLOCK + "\nend\n",
)
def test_pre_bottle_formula_has_no_bottle_block(self):
rendered = self.renderer()(TEMPLATE, VALUES)
self.assertNotIn("bottle do", rendered)
self.assertNotRegex(rendered, r"@[A-Z0-9_]+@")
def test_rejects_missing_and_undeclared_placeholders(self):
render = self.renderer()
with self.assertRaises(ValueError):
render(
TEMPLATE,
{key: value for key, value in VALUES.items() if key != "GIT_SHA"},
)
with self.assertRaises(ValueError):
render(TEMPLATE + "@UNKNOWN@", {**VALUES, "UNKNOWN": "anything"})
def test_rejects_template_tokens_in_bottle_block(self):
with self.assertRaises(ValueError):
self.renderer()(TEMPLATE, VALUES, BOTTLE_BLOCK.replace("sequoia", "@TAG@"))
def test_rejects_duplicate_bottle_blocks(self):
render = self.renderer()
for template, block in (
(TEMPLATE, BOTTLE_BLOCK + "\n" + BOTTLE_BLOCK),
(TEMPLATE + "\nbottle do\nend\n", BOTTLE_BLOCK),
(
TEMPLATE.replace("@BOTTLE_BLOCK@", "@BOTTLE_BLOCK@\n@BOTTLE_BLOCK@"),
BOTTLE_BLOCK,
),
):
with self.subTest(template=template, block=block):
with self.assertRaises(ValueError):
render(template, VALUES, block)
def test_rejects_empty_or_non_immutable_source_identity(self):
render = self.renderer()
for key, value in (
("SOURCE_SHA256", ""),
("SOURCE_SHA256", "bad"),
("GIT_SHA", ""),
("GIT_SHA", "abcdef1"),
("GIT_SHA", "develop"),
("SOURCE_REPOSITORY", 'apache/tsfile"'),
("HOMEBREW_VERSION", 'bad"\nversion "injected'),
("HOMEBREW_VERSION", "2.5.0.dev"),
("HOMEBREW_VERSION", "2.5.0rc1"),
):
with self.subTest(key=key, value=value):
with self.assertRaises(ValueError):
render(TEMPLATE, {**VALUES, key: value})
def test_preserves_ruby_interpolation_and_other_at_signs(self):
extra = "\n# maintainer@example.org\n# #{rpath}\n"
self.assertTrue(self.renderer()(TEMPLATE + extra, VALUES).endswith(extra))
def test_cli_renders_real_template_with_optional_bottle_file(self):
self.renderer()
template = TEMPLATE_PATH.read_text(encoding="utf-8")
self.assertIn(
'keg_only "the SDK installs its dependency header closure"',
template,
)
with tempfile.TemporaryDirectory() as temporary_directory:
directory = Path(temporary_directory)
output = directory / "tsfile.rb"
block = directory / "bottle.rb"
block.write_text(BOTTLE_BLOCK, encoding="utf-8")
command = [
sys.executable,
str(MODULE_PATH),
"--template",
str(TEMPLATE_PATH),
"--repository",
VALUES["SOURCE_REPOSITORY"],
"--git-sha",
VALUES["GIT_SHA"],
"--version",
VALUES["HOMEBREW_VERSION"],
"--source-sha256",
VALUES["SOURCE_SHA256"],
"--output",
str(output),
]
for bottle_args, expected_block in (
([], ""),
(["--bottle-block", str(block)], BOTTLE_BLOCK),
):
subprocess.run(command + bottle_args, check=True)
rendered = output.read_text(encoding="utf-8")
self.assertIn("class Tsfile < Formula", rendered)
self.assertIn(
'url "https://github.com/apache/tsfile/archive/abcdef1234567890abcdef1234567890abcdef12.tar.gz"',
rendered,
)
self.assertIn('version "2.5.0"', rendered)
self.assertIn(
'sha256 "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"',
rendered,
)
self.assertIn("#{rpath}", rendered)
self.assertNotRegex(rendered, r"@[A-Z0-9_]+@")
self.assertNotIn("\n\n\n", rendered)
if expected_block:
self.assertIn(BOTTLE_BLOCK, rendered)
else:
self.assertNotIn("bottle do", rendered)
if __name__ == "__main__":
unittest.main()