#!/usr/bin/env python # Copyright Advanced Micro Devices, Inc. # SPDX-License-Identifier: MIT """Unit tests for generate_local_index.py. Tests verify that index generation creates correct HTML structure with proper relative paths for local and parent files, and that multi-arch mode correctly processes directory structures. """ import os import sys import tempfile import unittest from contextlib import redirect_stderr from io import StringIO from pathlib import Path # Add build_tools/packaging/python to path so generate_local_index is importable. sys.path.insert(0, os.fspath(Path(__file__).parent.parent / "python")) from generate_local_index import ( generate_multiarch_indexes, generate_simple_index, main, ) class TestGenerateSimpleIndex(unittest.TestCase): """Tests for generate_simple_index().""" def test_local_files_only(self): """Test index with only local files.""" with tempfile.TemporaryDirectory() as tmp: output = Path(tmp) / "index.html" local_files = [ Path(tmp) / "package1-1.0.whl", Path(tmp) / "package2-2.0.tar.gz", ] generate_simple_index(output, local_files) content = output.read_text() self.assertIn("", content) self.assertIn("Package Index", content) self.assertIn('package1-1.0.whl', content) self.assertIn( 'package2-2.0.tar.gz', content ) def test_parent_files_only(self): """Test index with only parent files.""" with tempfile.TemporaryDirectory() as tmp: output = Path(tmp) / "index.html" parent_files = [ Path(tmp) / "parent1-1.0.whl", Path(tmp) / "parent2-2.0.tar.gz", ] generate_simple_index(output, [], parent_files=parent_files) content = output.read_text() self.assertIn('parent1-1.0.whl', content) self.assertIn( 'parent2-2.0.tar.gz', content ) def test_mixed_local_and_parent_files(self): """Test index with both local and parent files.""" with tempfile.TemporaryDirectory() as tmp: output = Path(tmp) / "index.html" local_files = [Path(tmp) / "local-1.0.whl"] parent_files = [Path(tmp) / "parent-1.0.whl"] generate_simple_index(output, local_files, parent_files=parent_files) content = output.read_text() self.assertIn('local-1.0.whl', content) self.assertIn('parent-1.0.whl', content) def test_custom_title(self): """Test index with custom title.""" with tempfile.TemporaryDirectory() as tmp: output = Path(tmp) / "index.html" generate_simple_index(output, [], title="Custom Title") content = output.read_text() self.assertIn("Custom Title", content) self.assertIn("

Custom Title

", content) def test_empty_lists(self): """Test index with no files.""" with tempfile.TemporaryDirectory() as tmp: output = Path(tmp) / "index.html" generate_simple_index(output, []) content = output.read_text() self.assertIn("", content) self.assertIn("Package Index", content) # Should have no tags self.assertNotIn("", content) self.assertIn("", content) self.assertIn("", content) self.assertIn('', content) self.assertIn("", content) self.assertIn("", content) self.assertIn("", content) self.assertIn("", content) # Check it ends with newline self.assertTrue(content.endswith("\n")) def test_plus_in_filename_is_url_encoded(self): """Test that '+' in filenames is percent-encoded in hrefs. PEP 440 local versions produce filenames like 'pkg-1.0+localver-py3-none-any.whl'. S3 interprets a literal '+' in URL paths as a space, so hrefs must encode '+' as '%2B'. """ with tempfile.TemporaryDirectory() as tmp: output = Path(tmp) / "index.html" local_files = [ Path(tmp) / "rocm_sdk_core-7.0.0+abc123-py3-none-linux_x86_64.whl", ] parent_files = [ Path(tmp) / "rocm_sdk_libs-7.0.0+abc123-py3-none-linux_x86_64.whl", ] generate_simple_index(output, local_files, parent_files=parent_files) content = output.read_text() # href should have %2B, not literal + self.assertIn( 'href="./rocm_sdk_core-7.0.0%2Babc123-py3-none-linux_x86_64.whl"', content, ) self.assertIn( 'href="../rocm_sdk_libs-7.0.0%2Babc123-py3-none-linux_x86_64.whl"', content, ) # Display text should still have literal + self.assertIn( ">rocm_sdk_core-7.0.0+abc123-py3-none-linux_x86_64.whl", content, ) class TestGenerateMultiarchIndexes(unittest.TestCase): """Tests for generate_multiarch_indexes().""" def test_generates_per_family_indexes(self): """Test that indexes are generated for each family subdirectory.""" with tempfile.TemporaryDirectory() as tmp: dist_dir = Path(tmp) # Create generic packages at top level (dist_dir / "rocm_sdk_core-1.0.whl").write_bytes(b"core") # Create family subdirectories with packages (dist_dir / "gfx94X-dcgpu").mkdir() (dist_dir / "gfx94X-dcgpu" / "rocm_libs_gfx94x-1.0.whl").write_bytes( b"gfx94x" ) (dist_dir / "gfx120X-all").mkdir() (dist_dir / "gfx120X-all" / "rocm_libs_gfx120x-1.0.whl").write_bytes( b"gfx120x" ) generate_multiarch_indexes(dist_dir) # Verify indexes were created self.assertTrue((dist_dir / "gfx94X-dcgpu" / "index.html").is_file()) self.assertTrue((dist_dir / "gfx120X-all" / "index.html").is_file()) def test_family_index_includes_local_and_parent(self): """Test that family indexes include both local and parent files.""" with tempfile.TemporaryDirectory() as tmp: dist_dir = Path(tmp) # Create generic packages (dist_dir / "rocm_sdk_core-1.0.whl").write_bytes(b"core") (dist_dir / "rocm_sdk_devel-1.0.whl").write_bytes(b"devel") # Create family directory (dist_dir / "gfx94X-dcgpu").mkdir() (dist_dir / "gfx94X-dcgpu" / "rocm_libs_gfx94x-1.0.whl").write_bytes( b"gfx94x" ) generate_multiarch_indexes(dist_dir) content = (dist_dir / "gfx94X-dcgpu" / "index.html").read_text() # Should have local file with ./ prefix self.assertIn( 'rocm_libs_gfx94x-1.0.whl', content, ) # Should have parent files with ../ prefix self.assertIn( 'rocm_sdk_core-1.0.whl', content ) self.assertIn( 'rocm_sdk_devel-1.0.whl', content, ) def test_custom_title_per_family(self): """Test that each family gets a custom title.""" with tempfile.TemporaryDirectory() as tmp: dist_dir = Path(tmp) (dist_dir / "gfx94X-dcgpu").mkdir() (dist_dir / "gfx120X-all").mkdir() generate_multiarch_indexes(dist_dir) content_94x = (dist_dir / "gfx94X-dcgpu" / "index.html").read_text() content_120x = (dist_dir / "gfx120X-all" / "index.html").read_text() self.assertIn("ROCm Python Packages - gfx94X-dcgpu", content_94x) self.assertIn("ROCm Python Packages - gfx120X-all", content_120x) def test_no_subdirectories_raises(self): """Test that missing family subdirectories raises an error.""" with tempfile.TemporaryDirectory() as tmp: dist_dir = Path(tmp) (dist_dir / "rocm_sdk_core-1.0.whl").write_bytes(b"core") with self.assertRaises(FileNotFoundError): generate_multiarch_indexes(dist_dir) def test_empty_family_directory(self): """Test family directory with no packages.""" with tempfile.TemporaryDirectory() as tmp: dist_dir = Path(tmp) (dist_dir / "rocm_sdk_core-1.0.whl").write_bytes(b"core") (dist_dir / "gfx94X-dcgpu").mkdir() # Empty directory generate_multiarch_indexes(dist_dir) # Index should still be created, just with no local files content = (dist_dir / "gfx94X-dcgpu" / "index.html").read_text() # Should have parent file self.assertIn( 'rocm_sdk_core-1.0.whl', content ) # Should not have any ./ links (no local files) self.assertNotIn('rocm_sdk_core-1.0.whl', content, ) self.assertIn( 'rocm_sdk_libraries-1.0.whl', content, ) self.assertIn( 'rocm_sdk_device_gfx942-1.0.whl', content, ) def test_default_honors_output(self): with tempfile.TemporaryDirectory() as tmp: dist_dir = Path(tmp) output = dist_dir / "custom.html" (dist_dir / "rocm_sdk_core-1.0.whl").write_bytes(b"core") main([os.fspath(dist_dir), "--output", os.fspath(output)]) self.assertTrue(output.is_file()) self.assertFalse((dist_dir / "index.html").exists()) def test_default_honors_patterns(self): with tempfile.TemporaryDirectory() as tmp: dist_dir = Path(tmp) (dist_dir / "rocm_sdk_core-1.0.whl").write_bytes(b"core") (dist_dir / "rocm-1.0.tar.gz").write_bytes(b"sdist") main([os.fspath(dist_dir), "--patterns", "*.whl"]) content = (dist_dir / "index.html").read_text() self.assertIn("rocm_sdk_core-1.0.whl", content) self.assertNotIn("rocm-1.0.tar.gz", content) def test_default_url_encodes_plus(self): with tempfile.TemporaryDirectory() as tmp: dist_dir = Path(tmp) filename = "rocm_sdk_core-7.0.0+abc123-py3-none-any.whl" (dist_dir / filename).write_bytes(b"core") main([os.fspath(dist_dir)]) content = (dist_dir / "index.html").read_text() self.assertIn( 'href="./rocm_sdk_core-7.0.0%2Babc123-py3-none-any.whl"', content, ) self.assertIn(f">{filename}", content) def test_per_family_indexes_generates_subdirectory_indexes(self): with tempfile.TemporaryDirectory() as tmp: dist_dir = Path(tmp) (dist_dir / "rocm_sdk_core-1.0.whl").write_bytes(b"core") family_dir = dist_dir / "gfx94X-dcgpu" family_dir.mkdir() (family_dir / "rocm_sdk_libraries_gfx94x_dcgpu-1.0.whl").write_bytes( b"libs" ) main([os.fspath(dist_dir), "--per-family-indexes"]) self.assertTrue((family_dir / "index.html").is_file()) self.assertFalse((dist_dir / "index.html").exists()) def test_per_family_indexes_rejects_output(self): with tempfile.TemporaryDirectory() as tmp: dist_dir = Path(tmp) (dist_dir / "gfx94X-dcgpu").mkdir() with redirect_stderr(StringIO()), self.assertRaises(SystemExit): main( [ os.fspath(dist_dir), "--per-family-indexes", "--output", os.fspath(dist_dir / "index.html"), ] ) def test_per_family_indexes_rejects_parent_dir(self): with tempfile.TemporaryDirectory() as tmp: dist_dir = Path(tmp) parent_dir = dist_dir / "parent" (dist_dir / "gfx94X-dcgpu").mkdir() with redirect_stderr(StringIO()), self.assertRaises(SystemExit): main( [ os.fspath(dist_dir), "--per-family-indexes", "--parent-dir", os.fspath(parent_dir), ] ) if __name__ == "__main__": unittest.main()