File: decisiongraph_invoker_test.py

package info (click to toggle)
chromium 139.0.7258.127-1
  • links: PTS, VCS
  • area: main
  • in suites:
  • size: 6,122,068 kB
  • sloc: cpp: 35,100,771; ansic: 7,163,530; javascript: 4,103,002; python: 1,436,920; asm: 946,517; xml: 746,709; pascal: 187,653; perl: 88,691; sh: 88,436; objc: 79,953; sql: 51,488; cs: 44,583; fortran: 24,137; makefile: 22,147; tcl: 15,277; php: 13,980; yacc: 8,984; ruby: 7,485; awk: 3,720; lisp: 3,096; lex: 1,327; ada: 727; jsp: 228; sed: 36
file content (202 lines) | stat: -rwxr-xr-x 7,939 bytes parent folder | download | duplicates (5)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
#!/usr/bin/env vpython3
# Copyright 2025 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

import unittest
import json
import sys
import os
from unittest.mock import patch, MagicMock

# We need to adjust the path to import the script directly.
sys.path.insert(0, os.path.abspath(os.path.dirname(__file__)))

import decisiongraph_invoker as dgi
import requests


class TestDecisionGraphInvoker(unittest.TestCase):

  def setUp(self):
    self.config_data = {
        "build_id": "12345",
        "change": 123456,
        "patchset": 1,
        "builder": "chromium-commit-queue",
        "api_key": "test_api_key_123"
    }
    self.config_file_path = "test_config.json"
    self.api_url_with_key = f"{dgi.API_URL}?key={self.config_data['api_key']}"

  def tearDown(self):
    if os.path.exists(self.config_file_path):
      os.remove(self.config_file_path)

  def _create_mock_config_file(self, data):
    """Helper to create a mock JSON config file."""
    with open(self.config_file_path, "w", encoding="utf-8") as f:
      json.dump(data, f)

  @patch('builtins.print')
  def test_load_config_from_json_success(self, mock_print):
    self._create_mock_config_file(self.config_data)
    config = dgi.load_config_from_json(self.config_file_path)
    self.assertEqual(config, self.config_data)

  @patch('sys.exit')
  @patch('builtins.print')
  def test_load_config_from_json_file_not_found(self, mock_print, mock_exit):
    dgi.load_config_from_json("non_existent_file.json")
    mock_print.assert_any_call(
        "Error: Configuration file not found at non_existent_file.json")
    mock_exit.assert_called_with(1)

  @patch('sys.exit')
  @patch('builtins.print')
  def test_load_config_from_json_missing_args(self, mock_print, mock_exit):
    incomplete_config = {"build_id": "123", "change": 1}
    self._create_mock_config_file(incomplete_config)
    dgi.load_config_from_json(self.config_file_path)
    mock_print.assert_any_call(
        f"Error: Missing required arguments in JSON config file.")
    mock_exit.assert_called_with(1)

  @patch('sys.exit')
  @patch('builtins.print')
  def test_load_config_from_json_invalid_type(self, mock_print, mock_exit):
    invalid_config = self.config_data.copy()
    invalid_config["change"] = "not_an_int"
    self._create_mock_config_file(invalid_config)
    dgi.load_config_from_json(self.config_file_path)
    mock_print.assert_any_call(
        "Error: Invalid data type for 'change' or 'patchset' in JSON."
        " Expected integers.")
    mock_exit.assert_called_with(1)

  @patch('builtins.print')
  @patch('requests.post')
  def test_fetch_api_data_success(self, mock_post, mock_print):
    mock_response = MagicMock()
    mock_response.status_code = 200
    mock_response.json.return_value = {"status": "success"}
    mock_response.text = '{"status": "success"}'
    mock_response.raise_for_status.return_value = None

    mock_post.return_value = mock_response

    response = dgi.fetch_api_data(self.api_url_with_key, {})
    self.assertEqual(response, {"status": "success"})
    mock_print.assert_any_call('{"status": "success"}')
    mock_print.assert_any_call(200)

  @patch('builtins.print')
  @patch('requests.post')
  def test_fetch_api_data_failure_http_error(self, mock_post, mock_print):
    # Create a mock response object for HTTPError.
    mock_response = MagicMock()
    mock_response.status_code = 500
    mock_response.text = "Internal Server Error"
    mock_response.raise_for_status.side_effect = (
        requests.exceptions.HTTPError("500 Server Error"))

    mock_post.return_value = mock_response

    response = dgi.fetch_api_data(self.api_url_with_key, {})
    self.assertIsNone(response)
    mock_print.assert_any_call(f"An error occurred: 500 Server Error")
    mock_print.assert_any_call("Internal Server Error")
    mock_print.assert_any_call(500)

  @patch('builtins.print')
  @patch('requests.post')
  def test_fetch_api_data_failure_request_exception(self, mock_post,
                                                    mock_print):
    # Simulate a network error (e.g., timeout, connection refused).
    mock_post.side_effect = requests.exceptions.ConnectionError(
        "Max retries exceeded")

    response = dgi.fetch_api_data(self.api_url_with_key, {})
    self.assertIsNone(response)
    mock_print.assert_called_with("An error occurred: Max retries exceeded")

  @patch('sys.exit')
  @patch('decisiongraph_invoker.load_config_from_json')
  @patch('decisiongraph_invoker.fetch_api_data')
  @patch('builtins.print')
  def test_main_single_batch_success(self, mock_print, mock_fetch_api_data,
                                     mock_load_config_from_json, mock_exit):
    mock_load_config_from_json.return_value = self.config_data
    mock_fetch_api_data.return_value = {"output": [{"result": "success"}]}

    with patch('sys.argv', [
        'decisiongraph_invoker.py', '--test-targets', 'browser_tests',
        'unit_tests', '--sts-config-file', self.config_file_path
    ]):
      dgi.main()
      mock_fetch_api_data.assert_called_once()
      mock_exit.assert_called_with(0)

  @patch('sys.exit')
  @patch('decisiongraph_invoker.load_config_from_json')
  @patch('decisiongraph_invoker.fetch_api_data')
  @patch('builtins.print')
  def test_main_multiple_batches_success(self, mock_print, mock_fetch_api_data,
                                         mock_load_config_from_json, mock_exit):
    mock_load_config_from_json.return_value = self.config_data
    mock_fetch_api_data.return_value = {"output": [{"result": "success"}]}

    # Simulate command-line arguments for multiple batches.
    test_targets = [f"test_{i}" for i in range(dgi.BATCH_SIZE + 2)]  # 2 batches
    with patch('sys.argv', ['decisiongraph_invoker.py', '--test-targets'] +
               test_targets + ['--sts-config-file', self.config_file_path]):
      dgi.main()
      self.assertEqual(mock_fetch_api_data.call_count, 2)  # Expect 2 calls
      mock_exit.assert_called_with(0)

  @patch('sys.exit')
  @patch('decisiongraph_invoker.load_config_from_json')
  @patch('decisiongraph_invoker.fetch_api_data')
  @patch('builtins.print')
  def test_main_api_failure(self, mock_print, mock_fetch_api_data,
                            mock_load_config_from_json, mock_exit):
    mock_load_config_from_json.return_value = self.config_data
    mock_fetch_api_data.return_value = None  # Simulate API failure.

    with patch('sys.argv', [
        'decisiongraph_invoker.py', '--test-targets', 'browser_tests',
        '--sts-config-file', self.config_file_path
    ]):
      dgi.main()
      mock_fetch_api_data.assert_called_once()
      mock_print.assert_any_call("Failed to fetch data from the API.")
      mock_exit.assert_called_with(1)

  @patch('sys.exit')
  @patch('decisiongraph_invoker.load_config_from_json')
  @patch('decisiongraph_invoker.fetch_api_data')
  @patch('builtins.print')
  def test_main_canonical_builder_conversion(self, mock_print,
                                             mock_fetch_api_data,
                                             mock_load_config_from_json,
                                             mock_exit):
    config_with_suffix = self.config_data.copy()
    config_with_suffix["builder"] = "linux-test-selection"
    mock_load_config_from_json.return_value = config_with_suffix
    mock_fetch_api_data.return_value = {"output": [{"result": "success"}]}

    with patch('sys.argv', [
        'decisiongraph_invoker.py', '--test-targets', 'browser_tests',
        '--sts-config-file', self.config_file_path
    ]):
      dgi.main()
      _, kwargs = mock_fetch_api_data.call_args
      payload = kwargs['json_payload']
      self.assertEqual(
          payload['input'][0]['input'][0]['checks'][0]['identifier']
          ['luci_test']['builder'], "linux")
      mock_exit.assert_called_with(0)


if __name__ == '__main__':
  unittest.main()