""" Test cases for o11ydiagnosis/subtasks/logreporter/task.py """ import os import sys import tempfile import shutil import unittest import json import sqlite3 from unittest.mock import patch, MagicMock, Mock, mock_open from datetime import datetime # Add project root to path sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '../../../../'))) from o11ydiagnosis.subtasks.logreporter.task import Task, run_cluster from o11ydiagnosis.subtasks.slowlogconverter.objstore import Local class TestTask(unittest.TestCase): """Test Task class""" def setUp(self): """Set up test fixtures""" self.temp_dir = tempfile.mkdtemp() self.local_path = os.path.join(self.temp_dir, "local") os.makedirs(self.local_path, exist_ok=True) self.mock_s3 = MagicMock() self.cluster = "test-cluster" self.bucket = "test-bucket" self.prefix = "test-prefix" self.task = Task( self.mock_s3, self.cluster, self.bucket, self.prefix, self.local_path ) def tearDown(self): """Clean up test fixtures""" if os.path.exists(self.temp_dir): shutil.rmtree(self.temp_dir) def test_task_initialization(self): """Test Task initialization""" self.assertEqual(self.task.s3, self.mock_s3) self.assertEqual(self.task.cluster, self.cluster) self.assertEqual(self.task.drhouse_bucket, self.bucket) self.assertEqual(self.task.drhouse_prefix, self.prefix) self.assertEqual(self.task.drhouse_local_path, self.local_path) self.assertEqual(self.task.existed_logdb_paths, {}) self.assertEqual(self.task.daily_data_json_file_paths, {}) self.assertEqual(self.task.benchmark_data_json_file_paths, {}) self.assertEqual(self.task.daily_report_html_file_paths, {}) self.assertEqual(self.task.all_statistic_file_paths, {}) def test_task_initialization_strips_trailing_slashes(self): """Test that trailing slashes are stripped""" task = Task( self.mock_s3, self.cluster, self.bucket, self.prefix + "/", self.local_path + "/" ) self.assertEqual(task.drhouse_prefix, self.prefix) self.assertEqual(task.drhouse_local_path, self.local_path) @patch('o11ydiagnosis.subtasks.logreporter.task.log') def test_prepare_downloads_logdb_files(self, mock_log): """Test prepare downloads logdb files""" report_hour_ts = 1609459200 # 2021-01-01 00:00:00 UTC # Mock download to return True (file exists) self.mock_s3.download.return_value = True self.task.prepare(7, report_hour_ts) # Should attempt to download: 24 hour files + 7 benchmark files + 1 all_statistic file = 32 self.assertEqual(self.mock_s3.download.call_count, 32) # Check that paths are tracked self.assertGreater(len(self.task.existed_logdb_paths), 0) @patch('o11ydiagnosis.subtasks.logreporter.task.log') def test_prepare_uses_existing_logdb_files(self, mock_log): """Test prepare uses existing local logdb files""" report_hour_ts = 1609459200 # Create a local file utc_hour = datetime.utcfromtimestamp(report_hour_ts).strftime("%Y%m%d%H") logdb_local_path = os.path.join( self.local_path, "log-stats", "1h", f"{utc_hour}.db" ) os.makedirs(os.path.dirname(logdb_local_path), exist_ok=True) with open(logdb_local_path, 'w') as f: f.write("test") self.task.prepare(7, report_hour_ts) # Should not download if file exists # But still should be tracked self.assertIn(logdb_local_path, self.task.existed_logdb_paths) @patch('o11ydiagnosis.subtasks.logreporter.task.log') def test_prepare_downloads_benchmark_files(self, mock_log): """Test prepare downloads benchmark files""" report_hour_ts = 1609459200 self.mock_s3.download.return_value = True self.task.prepare(7, report_hour_ts) # Should attempt to download benchmark files (7 days) # Plus 24 hour files, plus all_statistic file # Total calls should be at least 7 for benchmark download_calls = self.mock_s3.download.call_count self.assertGreaterEqual(download_calls, 7) @patch('o11ydiagnosis.subtasks.logreporter.task.log') def test_prepare_downloads_all_statistic_file(self, mock_log): """Test prepare downloads all statistic file""" report_hour_ts = 1609459200 self.mock_s3.download.return_value = True self.task.prepare(7, report_hour_ts) # Check that all_statistic file path is tracked all_statistic_path = os.path.join( self.local_path, "log-report", "all", f"{self.cluster}.csv" ) # May or may not exist depending on download, but should be tracked if downloaded # The important thing is that the method was called @patch('o11ydiagnosis.subtasks.logreporter.task.update_statistic_csv') @patch('o11ydiagnosis.subtasks.logreporter.task.send_feishu_message') @patch('o11ydiagnosis.subtasks.logreporter.task.generate_daily_statistic') @patch('o11ydiagnosis.subtasks.logreporter.task.generate_report') @patch('o11ydiagnosis.subtasks.logreporter.task.process_eigenvalue') @patch('o11ydiagnosis.subtasks.logreporter.task.process_daily_summary') @patch('o11ydiagnosis.subtasks.logreporter.task.log') def test_run_processes_data(self, mock_log, mock_process_summary, mock_process_eigen, mock_gen_report, mock_gen_stat, mock_send_feishu, mock_update_csv): """Test run processes data correctly""" report_hour_ts = 1609459200 # 2021-01-01 00:00:00 UTC # Setup mock return values mock_process_summary.return_value = [ {"component": "comp1", "level": "ERROR", "total": 100} ] mock_process_eigen.return_value = [ {"component": "comp1", "level": "ERROR", "total": 100, "tags": []} ] mock_gen_stat.return_value = { "all_logs_statistics": {"ERROR": {"comp1": 100}}, "new_logs_statistics": {"new_error": 0, "new_warn": 0} } # Create some logdb files for process_daily_summary utc_hour = datetime.utcfromtimestamp(report_hour_ts).strftime("%Y%m%d%H") logdb_path = os.path.join( self.local_path, "log-stats", "1h", f"{utc_hour}.db" ) os.makedirs(os.path.dirname(logdb_path), exist_ok=True) conn = sqlite3.connect(logdb_path) cursor = conn.cursor() cursor.execute('''CREATE TABLE IF NOT EXISTS log_stats (component TEXT, level TEXT, position TEXT, sample TEXT, count INTEGER)''') cursor.execute('''INSERT INTO log_stats VALUES (?, ?, ?, ?, ?)''', ("comp1", "ERROR", "pos1", "Error message", 100)) conn.commit() conn.close() self.task.existed_logdb_paths[logdb_path] = "s3-key" # Ensure directories exist utc_day = datetime.utcfromtimestamp(report_hour_ts).strftime("%Y%m%d") json_dir = os.path.join(self.local_path, "log-report", "1d") html_dir = os.path.join(self.local_path, "log-report", "1d") csv_dir = os.path.join(self.local_path, "log-report", "all") os.makedirs(json_dir, exist_ok=True) os.makedirs(html_dir, exist_ok=True) os.makedirs(csv_dir, exist_ok=True) self.task.run(7, report_hour_ts) # Verify functions were called mock_process_summary.assert_called_once() mock_process_eigen.assert_called_once() mock_gen_report.assert_called_once() mock_gen_stat.assert_called_once() mock_send_feishu.assert_called_once() mock_update_csv.assert_called_once() # Check that files are tracked self.assertGreater(len(self.task.daily_data_json_file_paths), 0) self.assertGreater(len(self.task.daily_report_html_file_paths), 0) @patch('o11ydiagnosis.subtasks.logreporter.task.log') def test_run_creates_daily_summary_json(self, mock_log): """Test run creates daily summary JSON file""" report_hour_ts = 1609459200 # Create logdb file utc_hour = datetime.utcfromtimestamp(report_hour_ts).strftime("%Y%m%d%H") logdb_path = os.path.join( self.local_path, "log-stats", "1h", f"{utc_hour}.db" ) os.makedirs(os.path.dirname(logdb_path), exist_ok=True) conn = sqlite3.connect(logdb_path) cursor = conn.cursor() cursor.execute('''CREATE TABLE IF NOT EXISTS log_stats (component TEXT, level TEXT, position TEXT, sample TEXT, count INTEGER)''') cursor.execute('''INSERT INTO log_stats VALUES (?, ?, ?, ?, ?)''', ("comp1", "ERROR", "pos1", "Error", 100)) conn.commit() conn.close() self.task.existed_logdb_paths[logdb_path] = "s3-key" # Ensure directory exists utc_day = datetime.utcfromtimestamp(report_hour_ts).strftime("%Y%m%d") json_dir = os.path.join(self.local_path, "log-report", "1d") os.makedirs(json_dir, exist_ok=True) self.task.run(7, report_hour_ts) # Check JSON file was created json_path = os.path.join(json_dir, f"{utc_day}.json") self.assertTrue(os.path.exists(json_path)) # Verify content with open(json_path, 'r') as f: data = json.load(f) self.assertIsInstance(data, list) @patch('o11ydiagnosis.subtasks.logreporter.task.log') def test_run_creates_html_report(self, mock_log): """Test run creates HTML report""" report_hour_ts = 1609459200 # Create logdb file utc_hour = datetime.utcfromtimestamp(report_hour_ts).strftime("%Y%m%d%H") logdb_path = os.path.join( self.local_path, "log-stats", "1h", f"{utc_hour}.db" ) os.makedirs(os.path.dirname(logdb_path), exist_ok=True) conn = sqlite3.connect(logdb_path) cursor = conn.cursor() cursor.execute('''CREATE TABLE IF NOT EXISTS log_stats (component TEXT, level TEXT, position TEXT, sample TEXT, count INTEGER)''') cursor.execute('''INSERT INTO log_stats VALUES (?, ?, ?, ?, ?)''', ("comp1", "ERROR", "pos1", "Error", 100)) conn.commit() conn.close() self.task.existed_logdb_paths[logdb_path] = "s3-key" # Ensure directory exists utc_day = datetime.utcfromtimestamp(report_hour_ts).strftime("%Y%m%d") html_dir = os.path.join(self.local_path, "log-report", "1d") os.makedirs(html_dir, exist_ok=True) self.task.run(7, report_hour_ts) # Check HTML file was created html_path = os.path.join(html_dir, f"{utc_day}.html") self.assertTrue(os.path.exists(html_path)) # Verify it's HTML with open(html_path, 'r') as f: content = f.read() self.assertIn("