| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282 |
- #!/usr/bin/env python3
- """
- Tests for coverage_check script.
- """
- import os
- import sys
- import unittest
- import subprocess
- import tempfile
- from unittest.mock import patch, MagicMock, call, mock_open
- # Add parent directory to path so we can import coverage modules
- sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
- from coverage_check import extract_coverage, compare_coverage, set_verbose, generate_comment, post_comment, set_github_output
- from coverage_check.util import log, file_exists, get_file_size, list_directory
- class TestCoverage(unittest.TestCase):
- # Class variables to store coverage files
- temp_dir = None
- extension_coverage_file = None
- webview_coverage_file = None
-
- @classmethod
- def setUpClass(cls):
- """Set up test environment once for all tests."""
- # Create temporary directory for test files
- cls.temp_dir = tempfile.TemporaryDirectory()
- cls.extension_coverage_file = os.path.join(cls.temp_dir.name, 'extension_coverage.txt')
- cls.webview_coverage_file = os.path.join(cls.temp_dir.name, 'webview_coverage.txt')
-
- # Run actual tests to generate coverage reports
- cls.generate_coverage_reports()
-
- # Verify files exist and are not empty
- assert os.path.exists(cls.extension_coverage_file), \
- f"Extension coverage file {cls.extension_coverage_file} does not exist"
- assert os.path.getsize(cls.extension_coverage_file) > 0, \
- f"Extension coverage file {cls.extension_coverage_file} is empty"
- assert os.path.exists(cls.webview_coverage_file), \
- f"Webview coverage file {cls.webview_coverage_file} does not exist"
- assert os.path.getsize(cls.webview_coverage_file) > 0, \
- f"Webview coverage file {cls.webview_coverage_file} is empty"
- @classmethod
- def tearDownClass(cls):
- """Clean up test environment after all tests."""
- if cls.temp_dir:
- cls.temp_dir.cleanup()
- @classmethod
- def generate_coverage_reports(cls):
- """Generate real coverage reports by running tests."""
- log("Generating coverage reports (this may take a while)...")
-
- # Run extension tests with coverage
- try:
- # Get absolute paths
- root_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), '../../..'))
- webview_dir = os.path.join(root_dir, 'webview-ui')
-
- # Use xvfb-run on Linux
- if sys.platform.startswith('linux'):
- cmd = f"cd {root_dir} && xvfb-run -a npm run test:coverage > {cls.extension_coverage_file} 2>&1"
- else:
- cmd = f"cd {root_dir} && npm run test:coverage > {cls.extension_coverage_file} 2>&1"
-
- log("Running extension tests...")
- log(f"Command: {cmd}")
- result = subprocess.run(cmd, shell=True, check=False, capture_output=True, text=True)
- log(f"Extension tests exit code: {result.returncode}")
-
- # Run webview tests with coverage
- log("Running webview tests...")
- cmd = f"cd {webview_dir} && npm run test:coverage > {cls.webview_coverage_file} 2>&1"
- log(f"Command: {cmd}")
- result = subprocess.run(cmd, shell=True, check=False, capture_output=True, text=True)
- log(f"Webview tests exit code: {result.returncode}")
-
- # Verify files were created
- if file_exists(cls.extension_coverage_file):
- ext_size = get_file_size(cls.extension_coverage_file)
- log(f"Extension coverage file created: {cls.extension_coverage_file} (size: {ext_size} bytes)")
- else:
- log(f"WARNING: Extension coverage file was not created: {cls.extension_coverage_file}")
-
- if file_exists(cls.webview_coverage_file):
- web_size = get_file_size(cls.webview_coverage_file)
- log(f"Webview coverage file created: {cls.webview_coverage_file} (size: {web_size} bytes)")
- else:
- log(f"WARNING: Webview coverage file was not created: {cls.webview_coverage_file}")
-
- log("Coverage reports generation completed.")
- except Exception as e:
- log(f"Error generating coverage reports: {e}")
- import traceback
- log(traceback.format_exc())
-
- # Create empty files if tests fail
- log("Creating fallback coverage files...")
- with open(cls.extension_coverage_file, 'w') as f:
- f.write("No coverage data available")
- with open(cls.webview_coverage_file, 'w') as f:
- f.write("No coverage data available")
- def test_extract_coverage(self):
- """Test extract_coverage function with both extension and webview coverage."""
- # Check if verbose mode is enabled
- if '-v' in sys.argv or '--verbose' in sys.argv:
- set_verbose(True)
-
- # Verify files exist before testing
- self.assertTrue(file_exists(self.extension_coverage_file),
- f"Extension coverage file does not exist: {self.extension_coverage_file}")
- self.assertTrue(file_exists(self.webview_coverage_file),
- f"Webview coverage file does not exist: {self.webview_coverage_file}")
-
- # Log file sizes
- ext_size = get_file_size(self.extension_coverage_file)
- web_size = get_file_size(self.webview_coverage_file)
- log(f"Extension coverage file size: {ext_size} bytes")
- log(f"Webview coverage file size: {web_size} bytes")
-
- # Test extension coverage
- log("Testing extension coverage extraction...")
- ext_coverage_pct = extract_coverage(self.extension_coverage_file, 'extension')
-
- # Check that coverage percentage is a float
- self.assertIsInstance(ext_coverage_pct, float)
-
- # Check that coverage percentage is between 0 and 100
- self.assertGreaterEqual(ext_coverage_pct, 0)
- self.assertLessEqual(ext_coverage_pct, 100)
-
- # Log coverage percentage for debugging
- log(f"Extension coverage: {ext_coverage_pct}%")
-
- # Test webview coverage
- log("Testing webview coverage extraction...")
- web_coverage_pct = extract_coverage(self.webview_coverage_file, 'webview')
-
- # Convert to float if it's an integer
- if isinstance(web_coverage_pct, int):
- web_coverage_pct = float(web_coverage_pct)
-
- # Check that coverage percentage is a float
- self.assertIsInstance(web_coverage_pct, float)
-
- # Check that coverage percentage is between 0 and 100
- self.assertGreaterEqual(web_coverage_pct, 0)
- self.assertLessEqual(web_coverage_pct, 100)
-
- # Log coverage percentage for debugging
- log(f"Webview coverage: {web_coverage_pct}%")
- def test_compare_coverage(self):
- """Test compare_coverage function."""
- # Test with coverage increase
- decreased, diff = compare_coverage(80, 90)
- self.assertFalse(decreased)
- self.assertEqual(diff, 10)
-
- # Test with coverage decrease
- decreased, diff = compare_coverage(90, 80)
- self.assertTrue(decreased)
- self.assertEqual(diff, 10)
-
- # Test with no change
- decreased, diff = compare_coverage(80, 80)
- self.assertFalse(decreased)
- self.assertEqual(diff, 0)
- def test_generate_comment(self):
- """Test generate_comment function."""
- comment = generate_comment(
- 80, 90, 'false', 10,
- 70, 75, 'false', 5
- )
-
- # Check that comment contains expected sections
- self.assertIn('Coverage Report', comment)
- self.assertIn('Extension Coverage', comment)
- self.assertIn('Webview Coverage', comment)
- self.assertIn('Overall Assessment', comment)
-
- # Check that comment contains coverage percentages
- self.assertIn('Base branch: 80%', comment)
- self.assertIn('PR branch: 90%', comment)
- self.assertIn('Base branch: 70%', comment)
- self.assertIn('PR branch: 75%', comment)
-
- # Check that comment contains correct assessment
- self.assertIn('Coverage increased or remained the same', comment)
- self.assertIn('Test coverage has been maintained or improved', comment)
- @patch('coverage_check.requests.get')
- @patch('coverage_check.requests.post')
- @patch('coverage_check.requests.patch')
- def test_post_comment_new(self, mock_patch, mock_post, mock_get):
- """Test post_comment function when creating a new comment."""
- # Create a temporary comment file
- comment_file = os.path.join(self.temp_dir.name, 'comment.md')
- with open(comment_file, 'w') as f:
- f.write('<!-- COVERAGE_REPORT -->\nTest comment')
-
- # Mock the API responses
- mock_get.return_value = MagicMock(status_code=200, json=lambda: [])
- mock_post.return_value = MagicMock(status_code=201)
-
- # Test post_comment function
- post_comment(comment_file, '123', 'owner/repo', 'token')
-
- # Check that the correct API calls were made
- mock_get.assert_called_once()
- mock_post.assert_called_once()
- mock_patch.assert_not_called()
- @patch('coverage_check.requests.get')
- @patch('coverage_check.requests.post')
- @patch('coverage_check.requests.patch')
- def test_post_comment_update(self, mock_patch, mock_post, mock_get):
- """Test post_comment function when updating an existing comment."""
- # Create a temporary comment file
- comment_file = os.path.join(self.temp_dir.name, 'comment.md')
- with open(comment_file, 'w') as f:
- f.write('<!-- COVERAGE_REPORT -->\nTest comment')
-
- # Mock the API responses
- mock_get.return_value = MagicMock(
- status_code=200,
- json=lambda: [{'id': 456, 'body': '<!-- COVERAGE_REPORT -->\nOld comment'}]
- )
- mock_patch.return_value = MagicMock(status_code=200)
-
- # Test post_comment function
- post_comment(comment_file, '123', 'owner/repo', 'token')
-
- # Check that the correct API calls were made
- mock_get.assert_called_once()
- mock_patch.assert_called_once()
- mock_post.assert_not_called()
- def test_set_github_output(self):
- """Test set_github_output function."""
- # Capture stdout
- with patch('sys.stdout', new=MagicMock()) as mock_stdout:
- # Mock environment without GITHUB_OUTPUT
- with patch.dict('os.environ', {}, clear=True):
- set_github_output('test_name', 'test_value')
-
- # Check that the correct output was printed to stdout
- mock_stdout.assert_has_calls([
- # GitHub Actions output format (deprecated method)
- call.write('::set-output name=test_name::test_value\n'),
- call.flush(),
- # Human readable format
- call.write('test_name: test_value\n'),
- call.flush()
- ], any_order=False)
-
- # Reset mock for next test
- mock_stdout.reset_mock()
-
- # Test with GITHUB_OUTPUT environment variable
- with patch.dict('os.environ', {'GITHUB_OUTPUT': '/tmp/github_output'}), \
- patch('builtins.open', mock_open()) as mock_file:
- set_github_output('test_name', 'test_value')
-
- # Check that file was written to
- mock_file.assert_called_once_with('/tmp/github_output', 'a')
- mock_file().write.assert_called_once_with('test_name=test_value\n')
-
- # Check that human readable output was printed
- mock_stdout.assert_has_calls([
- call.write('test_name: test_value\n'),
- call.flush()
- ], any_order=False)
- if __name__ == '__main__':
- unittest.main()
|