blob: 2c43922c99f02697c895c27a64fef7c016a31aa7 [file]
#!/usr/bin/env vpython3
# Copyright 2026 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Unit tests for get_task_digest.py."""
import contextlib
import io
import json
import sys
import unittest
from unittest import mock
import get_task_digest as gtd
class GetTaskDigestTest(unittest.TestCase):
"""Unit tests for get_task_digest.py."""
def setUp(self) -> None:
"""Sets up subprocess run mocks."""
self.run_patcher = mock.patch('subprocess.run')
self.mock_run = self.run_patcher.start()
self.addCleanup(self.run_patcher.stop)
def test_input_properties_success(self) -> None:
"""Verifies hash extraction from input swarm_hashes."""
data = {
'input': {
'properties': {
'swarm_hashes': {
'net_unittests': 'abc123/810'
}
}
}
}
mock_proc = mock.MagicMock(returncode=0, stdout=json.dumps(data))
self.mock_run.return_value = mock_proc
res = gtd.get_digest_from_properties('123', 'net_unittests')
self.assertEqual(res, 'abc123')
def test_output_properties_success(self) -> None:
"""Verifies fallback to output properties when input misses."""
data_in = {'input': {'properties': {'swarm_hashes': {}}}}
data_out = {'output': {'properties': {'net_unittests': 'def456/999'}}}
proc_in = mock.MagicMock(returncode=0, stdout=json.dumps(data_in))
proc_out = mock.MagicMock(returncode=0, stdout=json.dumps(data_out))
self.mock_run.side_effect = [proc_in, proc_out]
res = gtd.get_digest_from_properties('123', 'net_unittests')
self.assertEqual(res, 'def456')
def test_output_swarm_hashes(self) -> None:
"""Verifies extraction from output swarm_hashes."""
data_in = {'input': {'properties': {}}}
data_out = {
'output': {
'properties': {
'swarm_hashes': {
'net_unittests': 'hash111/50'
}
}
}
}
proc_in = mock.MagicMock(returncode=0, stdout=json.dumps(data_in))
proc_out = mock.MagicMock(returncode=0, stdout=json.dumps(data_out))
self.mock_run.side_effect = [proc_in, proc_out]
res = gtd.get_digest_from_properties('123', 'net_unittests')
self.assertEqual(res, 'hash111')
def test_output_swarming_trigger_properties(self) -> None:
"""Verifies extraction from swarming_trigger_properties."""
data_in = {'input': {'properties': {}}}
data_out = {
'output': {
'properties': {
'swarming_trigger_properties': {
'swarm_hashes': {
'net_unittests': 'hash789/100'
}
}
}
}
}
proc_in = mock.MagicMock(returncode=0, stdout=json.dumps(data_in))
proc_out = mock.MagicMock(returncode=0, stdout=json.dumps(data_out))
self.mock_run.side_effect = [proc_in, proc_out]
res = gtd.get_digest_from_properties('123', 'net_unittests')
self.assertEqual(res, 'hash789')
def test_orchestrator_child_build(self) -> None:
"""Verifies fallback to steps scan for child build."""
data_in1 = {'input': {'properties': {}}}
data_out1 = {'output': {'properties': {}}}
data_steps = {
'steps': [{
'summaryMarkdown': '* [8000000000000000002](url)'
}]
}
data_in2 = {'input': {'properties': {}}}
data_out2 = {'output': {'properties': {'net_unittests': 'childhash/999'}}}
proc_in1 = mock.MagicMock(returncode=0, stdout=json.dumps(data_in1))
proc_out1 = mock.MagicMock(returncode=0, stdout=json.dumps(data_out1))
proc_steps = mock.MagicMock(returncode=0, stdout=json.dumps(data_steps))
proc_in2 = mock.MagicMock(returncode=0, stdout=json.dumps(data_in2))
proc_out2 = mock.MagicMock(returncode=0, stdout=json.dumps(data_out2))
self.mock_run.side_effect = [
proc_in1, proc_out1, proc_steps, proc_in2, proc_out2
]
res = gtd.get_digest_from_properties('orch', 'net_unittests')
self.assertEqual(res, 'childhash')
def test_bad_json_handling(self) -> None:
"""Verifies JSONDecodeError handling across bb calls."""
proc_bad = mock.MagicMock(returncode=0, stdout='invalid json')
self.mock_run.side_effect = [proc_bad, proc_bad, proc_bad]
res = gtd.get_digest_from_properties('123', 'net_unittests')
self.assertIsNone(res)
def test_bb_cli_failure(self) -> None:
"""Verifies handling when bb CLI returns non-zero code."""
proc_fail = mock.MagicMock(returncode=1, stdout='')
self.mock_run.side_effect = [proc_fail, proc_fail, proc_fail]
res = gtd.get_digest_from_properties('123', 'net_unittests')
self.assertIsNone(res)
@mock.patch('get_task_digest.get_digest_from_properties', return_value=None)
def test_main_not_found(self, _) -> None:
"""Verifies sys.exit(1) when digest is missing."""
err_buf = io.StringIO()
with mock.patch.object(sys, 'argv',
['gtd.py', '--build', '1', '--step', 's']):
with contextlib.redirect_stderr(err_buf):
with self.assertRaises(SystemExit) as cm:
gtd.main()
self.assertEqual(cm.exception.code, 1)
self.assertIn('not found', err_buf.getvalue())
@mock.patch('get_task_digest.get_digest_from_properties',
return_value='hash789')
def test_main_success(self, _) -> None:
"""Verifies digest is printed to stdout."""
out_buf = io.StringIO()
with mock.patch.object(sys, 'argv',
['gtd.py', '--build', '1', '--step', 's']):
with contextlib.redirect_stdout(out_buf):
gtd.main()
self.assertIn('hash789', out_buf.getvalue())
if __name__ == '__main__':
unittest.main()