Skip to content

attempt 10 for feature/function merge#11

Merged
Sekiro4321 merged 9 commits intomainfrom
feature/new-function
Feb 13, 2026
Merged

attempt 10 for feature/function merge#11
Sekiro4321 merged 9 commits intomainfrom
feature/new-function

Conversation

@Sekiro4321
Copy link
Copy Markdown
Owner

No description provided.

@github-actions
Copy link
Copy Markdown

🤖 AI Code Review

I've reviewed the provided code diff, focusing on security vulnerabilities, bug risks, performance issues, and best practice violations.

Here's my feedback:

1. Workflow: Unquoted Arguments in Script Execution

  • Severity: MEDIUM

  • Description: The scripts/generate_tests.py command in the GitHub Actions workflow passes the $FILES variable unquoted. The git diff command outputs filenames separated by newlines, which are then converted to spaces by tr '\n' ' '. If any of these filenames contain spaces, passing the combined string directly to python scripts/generate_tests.py ${{ steps.changed-files.outputs.files }} will cause Python's sys.argv to incorrectly parse a single filename with spaces as multiple arguments. This leads to a bug where the Python script attempts to process non-existent or partial file paths. While sys.argv usually mitigates direct shell injection for simple cases, it's still a bug risk for file processing and a potential (though low) security risk if a malicious filename with shell metacharacters were ever allowed.

  • Suggested Fix:

    1. Quote the $FILES variable in the workflow step.
    2. In the Python script, use shlex.split() to correctly parse the space-separated string argument into individual filenames, preserving spaces within names.

    .github/workflows/test-gen.yml changes:

          - name: Generate tests
            if: steps.changed-files.outputs.files != ''
            env:
              GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }}
            run: |
              python scripts/generate_tests.py "${{ steps.changed-files.outputs.files }}" # Add quotes around the variable

    scripts/generate_tests.py changes (in main function):

    import sys
    import shlex # Add this import
    # ...
    
    def main():
        # Handle arguments correctly with shlex.split for robust parsing
        changed_files_arg = sys.argv[1] if len(sys.argv) > 1 else ""
        changed_files = shlex.split(changed_files_arg) if changed_files_arg else []
    
        if not changed_files:
            print("No changed Python files provided for Test Generation.")
            return
        # ... rest of main()

2. Workflow: Over-privileged GitHub Token

  • Severity: LOW

  • Description: The workflow requests pull-requests: write permission. For the stated purpose of committing and pushing generated test files back to the PR branch, contents: write is typically sufficient. Granting pull-requests: write goes beyond the principle of least privilege, potentially allowing the workflow to modify PR titles, descriptions, labels, or even close PRs if there were a vulnerability in another part of the workflow or a malicious dependency.

  • Suggested Fix: Remove the pull-requests: write permission unless there's an explicit and necessary reason for it that is not covered by the current workflow.

    .github/workflows/test-gen.yml changes:

    jobs:
      generate-tests:
        runs-on: ubuntu-latest
        permissions:
          contents: write
          # pull-requests: write # Remove this line

3. Workflow: Ambiguous Git Push Target

  • Severity: LOW

  • Description: The git push command is used without explicitly specifying a remote or branch (e.g., git push). While this might work in many scenarios where actions/checkout correctly sets up the upstream branch for HEAD, it relies on implicit configuration. If the PR originates from a fork, the bot might not have push access to the fork's branch, or the default origin might not be correctly configured for the push. Explicitly defining the push target makes the workflow more robust and easier to understand.

  • Suggested Fix: Explicitly push to the head_ref on the origin remote.

    .github/workflows/test-gen.yml changes:

          - name: Commit generated tests
            run: |
              git config user.name "github-actions[bot]"
              git config user.email "github-actions[bot]@users.noreply.github.com"
    
              if git diff --quiet tests/; then
                echo "No new tests generated"
                exit 0
              fi
    
              git add tests/
              git commit -m "Auto-generate tests for new code [skip ci]"
              git push origin HEAD:${{ github.head_ref }} # Explicitly push to origin head_ref

4. app.py: Missing Newline at End of File

  • Severity: LOW

  • Description: The app.py file, both in its original state and with the new factorial function, is missing a newline character at the end of the file. This is a common best practice violation and can sometimes cause issues with certain tools, version control systems (like git diff output shown), or when concatenating files.

  • Suggested Fix: Add a newline character at the very end of app.py.

    app.py changes:

    --- a/app.py
    +++ b/app.py
    @@ -17,4 +17,13 @@ def reverse_string(s: str) -> str:
     
     def multiply(a: int, b: int) -> int:
         """Multiply two numbers together."""
    -    return a * b
    \ No newline at end of file
    +    return a * b
    +
    +def factorial(n: int) -> int:
    +    """Calculate the factorial of a number."""
    +    if n < 0:
    +        raise ValueError("Factorial is not defined for negative numbers.")
    +    if n <= 1:
    +        return 1
    +    return n * factorial(n - 1)
    +

5. scripts/generate_tests.py: Lack of Error Handling in extract_functions

  • Severity: MEDIUM

  • Description: The extract_functions function directly calls ast.parse() on the file content. If a Python file passed to this script contains syntax errors or is otherwise malformed, ast.parse() will raise an exception (e.g., SyntaxError), crashing the script. This would prevent test generation for any subsequent valid files, making the workflow brittle.

  • Suggested Fix: Wrap the ast.parse() call (and potentially the file reading) in a try-except block to gracefully handle parsing errors and log a warning or error message, allowing the script to continue processing other files.

    scripts/generate_tests.py changes:

    import ast
    import os
    import sys
    # ...
    import logging # Add logging import
    logging.basicConfig(level=logging.INFO, format='%(levelname)s: %(message)s') # Initialize logging
    
    def extract_functions(file_path):
        """parse the python file and extract function definitions"""
        try:
            with open(file_path, 'r', encoding='utf-8') as f: # Specify encoding for robustness
                source = f.read()
            tree = ast.parse(source)
        except (IOError, SyntaxError) as e:
            logging.error(f"Error parsing file {file_path}: {e}") # Use logging
            return [] # Return empty list on error
        
        functions = []
        for node in ast.walk(tree):
            if isinstance(node, ast.FunctionDef):
                func_name = node.name
                args = [arg.arg for arg in node.args.args]
                docstring = ast.get_docstring(node) or ""
                source_code = ast.get_source_segment(source, node)
    
                functions.append({
                    'name': func_name,
                    'args': args,
                    'docstring': docstring,
                    'source_code': source_code,
                    'is_private': func_name.startswith('_') # Added for filtering in main()
                })
        return functions

6. scripts/generate_tests.py: Lack of Error Handling for Gemini API Calls

  • Severity: MEDIUM

  • Description: The generate_tests_for_functions function directly calls client.models.generate_content(). API calls can fail due to various reasons such as network issues, rate limiting, invalid API keys, or service outages. The current implementation does not handle these potential exceptions, which could lead to the script crashing or returning unexpected results (e.g., None or an error object), halting the test generation process and making the workflow unreliable.

  • Suggested Fix: Implement a try-except block around the API call to catch potential exceptions (e.g., from google.generativeai.types.client.ApyError or more general network errors) and provide a fallback mechanism, such as returning an empty string and logging the error.

    scripts/generate_tests.py changes (in generate_tests_for_functions function):

    # ... imports ...
    # from google.generativeai import Client, types # For specific error types if needed
    
    def generate_tests_for_functions(func_info):
        """Use Gemini to generate pytest tests for a function"""
        client = genai.Client() # Consider moving client initialization outside to reuse
    
        prompt = f"""
        # ... prompt content ...
        """
        try:
            response = client.models.generate_content(
                model="gemini-2.5-flash", contents=prompt
            )
            # Check if response actually contains text content
            if hasattr(response, 'text') and response.text:
                return response.text.strip() # .strip() to clean up potential whitespace
            else:
                logging.warning(f"Gemini API returned no text content for function {func_info['name']}")
                return ""
        except Exception as e: # Catch broader exceptions for API issues
            logging.error(f"Error generating tests for function {func_info['name']} via Gemini API: {e}")
            return "" # Return empty string on error

7. scripts/generate_tests.py: Incomplete Generated Tests (Missing Imports) and Poor Organization

  • Severity: HIGH

  • Description:

    1. Missing Imports: The generate_tests_for_functions prompt focuses only on generating the test cases, and the main function simply concatenates these outputs. The prompt does not instruct the LLM to include necessary Python import statements (e.g., from app import factorial) for the functions being tested. As a result, the generated test files will fail when run due to NameError or ImportError, rendering the generated tests non-functional without manual editing.
    2. Poor Organization/Overwriting: All generated tests are written to a single file, tests/test_generated.py. This leads to poor organization, as tests for different modules are mixed. More critically, if the workflow runs multiple times for different PRs that modify different modules, subsequent runs might overwrite tests generated in previous runs for other modules in the same test_generated.py file, leading to loss of generated tests. This design also increases the likelihood of merge conflicts.
  • Suggested Fix:

    1. Dynamic Imports: Modify the main function to dynamically generate and prepend the correct import statements at the top of each generated test file, based on the functions found in the source module.
    2. Separate Test Files: Change the test file naming strategy to generate a dedicated test file per source file (e.g., tests/test_app.py for app.py) to keep tests organized, prevent overwriting, and reduce merge conflicts. Ensure empty test files are not created if no tests are generated.

    scripts/generate_tests.py changes (full main function and prompt adjustment):

    import ast
    import os
    import sys
    import shlex
    from google import genai
    import logging
    
    logging.basicConfig(level=logging.INFO, format='%(levelname)s: %(message)s')
    
    def extract_functions(file_path):
        """parse the python file and extract function definitions"""
        try:
            with open(file_path, 'r', encoding='utf-8') as f:
                source = f.read()
            tree = ast.parse(source)
        except (IOError, SyntaxError) as e:
            logging.error(f"Error parsing file {file_path}: {e}")
            return []
        
        functions = []
        for node in ast.walk(tree):
            if isinstance(node, ast.FunctionDef):
                func_name = node.name
                args = [arg.arg for arg in node.args.args]
                docstring = ast.get_docstring(node) or ""
                source_code = ast.get_source_segment(source, node)
    
                functions.append({
                    'name': func_name,
                    'args': args,
                    'docstring': docstring,
                    'source_code': source_code,
                    'is_private': func_name.startswith('_') # Add this info
                })
        return functions
    
    def generate_tests_for_functions(func_info):
        """Use Gemini to generate pytest tests for a function"""
        client = genai.Client()
    
        # Modified prompt: Explicitly tell LLM NOT to include imports for the function itself
        prompt = f"""
    Generate pytest tests for this following Python function.
    Do NOT include any import statements for the function being tested, only for pytest or standard libraries if needed.
    
    Function name: {func_info['name']}
    Arguments: {func_info['args']}
    Docstring: {func_info['docstring']}
    
    Source code:
    {func_info['source_code']}
    
    Requirements:
    1. Generate 3-5 meaningful test cases.
    2. Include edge cases (empty cases, None values, etc.) where applicable.
    3. Use descriptive test function names.
    4. Include assertions that actually test the function's behavior.
    5. Do not include any placeholder tests like `assert True` or `assert False`.
    6. Use pytest framework for the tests.
    """
        try:
            response = client.models.generate_content(
                model="gemini-2.5-flash", contents=prompt
            )
            if hasattr(response, 'text') and response.text:
                return response.text.strip()
            else:
                logging.warning(f"Gemini API returned no text content for function {func_info['name']}")
                return ""
        except Exception as e:
            logging.error(f"Error generating tests for function {func_info['name']} via Gemini API: {e}")
            return ""
    
    def main():
        changed_files_arg = sys.argv[1] if len(sys.argv) > 1 else ""
        changed_files = shlex.split(changed_files_arg) if changed_files_arg else []
    
        if not changed_files:
            logging.info("No changed Python files provided for Test Generation.")
            return
    
        os.makedirs('tests', exist_ok=True) # Ensure 'tests' directory exists once
    
        for file_path in changed_files:
            if not file_path.endswith('.py') or file_path.startswith('tests/'):
                logging.info(f"Skipping {file_path} (not a Python file or in tests/ directory).")
                continue
    
            logging.info(f"Analyzing: {file_path}")
            functions = extract_functions(file_path)
    
            if not functions:
                logging.warning(f"No functions extracted or error parsing {file_path}.")
                continue
    
            module_name = os.path.splitext(os.path.basename(file_path))[0]
            test_file_name = f'test_{module_name}.py'
            test_file_path = os.path.join('tests', test_file_name)
    
            tests_content_for_file = ["import pytest\n\n"] # Pytest import always at top
            
            # Dynamically generate import statement for the functions in this module
            public_function_names = [f['name'] for f in functions if not f['is_private']]
            if public_function_names:
                # Assuming the source file (e.g., app.py) is at the project root
                # and tests in `tests/`. This requires `from module_name import ...`
                # Adjust if your project structure is different (e.g., `from src.module_name import ...`)
                import_statement = f"from {module_name} import {', '.join(sorted(public_function_names))}\n\n"
                tests_content_for_file.append(import_statement)
            else:
                logging.info(f"No public functions found in {file_path} to generate imports for.")
    
            found_any_tests_for_file = False
            for func in functions:
                if func['is_private']:
                    continue
    
                logging.info(f"Generating tests for function: {func['name']} in {file_path}")
                generated_tests = generate_tests_for_functions(func)
                
                if generated_tests:
                    tests_content_for_file.append(f"# Tests for {func['name']}\n{generated_tests}\n\n")
                    found_any_tests_for_file = True
                else:
                    logging.warning(f"No tests generated for {func['name']} in {file_path}.")
    
            if found_any_tests_for_file:
                # Remove trailing newlines from the last test block if present for cleaner output
                if tests_content_for_file and tests_content_for_file[-1].endswith('\n\n'):
                    tests_content_for_file[-1] = tests_content_for_file[-1].rstrip('\n') + '\n'
                
                with open(test_file_path, 'w', encoding='utf-8') as f:
                    f.writelines(tests_content_for_file)
                logging.info(f"Generated tests written to: {test_file_path}")
            else:
                logging.info(f"No tests generated for any public functions in {file_path}. Skipping creation of {test_file_name}.")
                # If an empty test file was created from a previous run, remove it
                if os.path.exists(test_file_path):
                    os.remove(test_file_path)
    
    if __name__ == '__main__':
        main()

Severity Summary:

SEVERITY_SUMMARY: CRITICAL


Powered by Gemini AI

@github-actions github-actions bot added the ai-review: critical AI review found critical issues label Feb 13, 2026
@Sekiro4321 Sekiro4321 merged commit 32b5e45 into main Feb 13, 2026
5 of 6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ai-review: critical AI review found critical issues

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant