blob: fd9250a339b5b521567f3d14e64d0ffc147235a9 [file] [log] [blame]
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Copyright 2019 The ChromiumOS Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Tests when updating a tryjob's status."""
import json
import os
import subprocess
import unittest
import unittest.mock as mock
from test_helpers import CreateTemporaryJsonFile
from test_helpers import WritePrettyJsonFile
import update_tryjob_status
from update_tryjob_status import CustomScriptStatus
from update_tryjob_status import TryjobStatus
class UpdateTryjobStatusTest(unittest.TestCase):
"""Unittests for updating a tryjob's 'status'."""
def testFoundTryjobIndex(self):
test_tryjobs = [
{
"rev": 123,
"url": "https://some_url_to_CL.com",
"cl": "https://some_link_to_tryjob.com",
"status": "good",
"buildbucket_id": 91835,
},
{
"rev": 1000,
"url": "https://some_url_to_CL.com",
"cl": "https://some_link_to_tryjob.com",
"status": "pending",
"buildbucket_id": 10931,
},
]
expected_index = 0
revision_to_find = 123
self.assertEqual(
update_tryjob_status.FindTryjobIndex(
revision_to_find, test_tryjobs
),
expected_index,
)
def testNotFindTryjobIndex(self):
test_tryjobs = [
{
"rev": 500,
"url": "https://some_url_to_CL.com",
"cl": "https://some_link_to_tryjob.com",
"status": "bad",
"buildbucket_id": 390,
},
{
"rev": 10,
"url": "https://some_url_to_CL.com",
"cl": "https://some_link_to_tryjob.com",
"status": "skip",
"buildbucket_id": 10,
},
]
revision_to_find = 250
self.assertIsNone(
update_tryjob_status.FindTryjobIndex(revision_to_find, test_tryjobs)
)
@mock.patch.object(subprocess, "Popen")
# Simulate the behavior of `os.rename()` when successfully renamed a file.
@mock.patch.object(os, "rename", return_value=None)
# Simulate the behavior of `os.path.basename()` when successfully retrieved
# the basename of the temp .JSON file.
@mock.patch.object(os.path, "basename", return_value="tmpFile.json")
def testInvalidExitCodeByCustomScript(
self, mock_basename, mock_rename_file, mock_exec_custom_script
):
error_message_by_custom_script = "Failed to parse .JSON file"
# Simulate the behavior of 'subprocess.Popen()' when executing the custom
# script.
#
# `Popen.communicate()` returns a tuple of `stdout` and `stderr`.
mock_exec_custom_script.return_value.communicate.return_value = (
None,
error_message_by_custom_script,
)
# Exit code of 1 is not in the mapping, so an exception will be raised.
custom_script_exit_code = 1
mock_exec_custom_script.return_value.returncode = (
custom_script_exit_code
)
tryjob_contents = {
"status": "good",
"rev": 1234,
"url": "https://some_url_to_CL.com",
"link": "https://some_url_to_tryjob.com",
}
custom_script_path = "/abs/path/to/script.py"
status_file_path = "/abs/path/to/status_file.json"
name_json_file = os.path.join(
os.path.dirname(status_file_path), "tmpFile.json"
)
expected_error_message = (
"Custom script %s exit code %d did not match "
'any of the expected exit codes: %s for "good", '
'%d for "bad", or %d for "skip".\nPlease check '
"%s for information about the tryjob: %s"
% (
custom_script_path,
custom_script_exit_code,
CustomScriptStatus.GOOD.value,
CustomScriptStatus.BAD.value,
CustomScriptStatus.SKIP.value,
name_json_file,
error_message_by_custom_script,
)
)
# Verify the exception is raised when the exit code by the custom script
# does not match any of the exit codes in the mapping of
# `custom_script_exit_value_mapping`.
with self.assertRaises(ValueError) as err:
update_tryjob_status.GetCustomScriptResult(
custom_script_path, status_file_path, tryjob_contents
)
self.assertEqual(str(err.exception), expected_error_message)
mock_exec_custom_script.assert_called_once()
mock_rename_file.assert_called_once()
mock_basename.assert_called_once()
@mock.patch.object(subprocess, "Popen")
# Simulate the behavior of `os.rename()` when successfully renamed a file.
@mock.patch.object(os, "rename", return_value=None)
# Simulate the behavior of `os.path.basename()` when successfully retrieved
# the basename of the temp .JSON file.
@mock.patch.object(os.path, "basename", return_value="tmpFile.json")
def testValidExitCodeByCustomScript(
self, mock_basename, mock_rename_file, mock_exec_custom_script
):
# Simulate the behavior of 'subprocess.Popen()' when executing the custom
# script.
#
# `Popen.communicate()` returns a tuple of `stdout` and `stderr`.
mock_exec_custom_script.return_value.communicate.return_value = (
None,
None,
)
mock_exec_custom_script.return_value.returncode = (
CustomScriptStatus.GOOD.value
)
tryjob_contents = {
"status": "good",
"rev": 1234,
"url": "https://some_url_to_CL.com",
"link": "https://some_url_to_tryjob.com",
}
custom_script_path = "/abs/path/to/script.py"
status_file_path = "/abs/path/to/status_file.json"
self.assertEqual(
update_tryjob_status.GetCustomScriptResult(
custom_script_path, status_file_path, tryjob_contents
),
TryjobStatus.GOOD.value,
)
mock_exec_custom_script.assert_called_once()
mock_rename_file.assert_not_called()
mock_basename.assert_not_called()
def testNoTryjobsInStatusFileWhenUpdatingTryjobStatus(self):
bisect_test_contents = {"start": 369410, "end": 369420, "jobs": []}
# Create a temporary .JSON file to simulate a .JSON file that has bisection
# contents.
with CreateTemporaryJsonFile() as temp_json_file:
with open(temp_json_file, "w") as f:
WritePrettyJsonFile(bisect_test_contents, f)
revision_to_update = 369412
custom_script = None
# Verify the exception is raised when the `status_file` does not have any
# `jobs` (empty).
with self.assertRaises(SystemExit) as err:
update_tryjob_status.UpdateTryjobStatus(
revision_to_update,
TryjobStatus.GOOD,
temp_json_file,
custom_script,
)
self.assertEqual(
str(err.exception), "No tryjobs in %s" % temp_json_file
)
# Simulate the behavior of `FindTryjobIndex()` when the tryjob does not exist
# in the status file.
@mock.patch.object(
update_tryjob_status, "FindTryjobIndex", return_value=None
)
def testNotFindTryjobIndexWhenUpdatingTryjobStatus(
self, mock_find_tryjob_index
):
bisect_test_contents = {
"start": 369410,
"end": 369420,
"jobs": [{"rev": 369411, "status": "pending"}],
}
# Create a temporary .JSON file to simulate a .JSON file that has bisection
# contents.
with CreateTemporaryJsonFile() as temp_json_file:
with open(temp_json_file, "w") as f:
WritePrettyJsonFile(bisect_test_contents, f)
revision_to_update = 369416
custom_script = None
# Verify the exception is raised when the `status_file` does not have any
# `jobs` (empty).
with self.assertRaises(ValueError) as err:
update_tryjob_status.UpdateTryjobStatus(
revision_to_update,
TryjobStatus.SKIP,
temp_json_file,
custom_script,
)
self.assertEqual(
str(err.exception),
"Unable to find tryjob for %d in %s"
% (revision_to_update, temp_json_file),
)
mock_find_tryjob_index.assert_called_once()
# Simulate the behavior of `FindTryjobIndex()` when the tryjob exists in the
# status file.
@mock.patch.object(update_tryjob_status, "FindTryjobIndex", return_value=0)
def testSuccessfullyUpdatedTryjobStatusToGood(self, mock_find_tryjob_index):
bisect_test_contents = {
"start": 369410,
"end": 369420,
"jobs": [{"rev": 369411, "status": "pending"}],
}
# Create a temporary .JSON file to simulate a .JSON file that has bisection
# contents.
with CreateTemporaryJsonFile() as temp_json_file:
with open(temp_json_file, "w") as f:
WritePrettyJsonFile(bisect_test_contents, f)
revision_to_update = 369411
# Index of the tryjob that is going to have its 'status' value updated.
tryjob_index = 0
custom_script = None
update_tryjob_status.UpdateTryjobStatus(
revision_to_update,
TryjobStatus.GOOD,
temp_json_file,
custom_script,
)
# Verify that the tryjob's 'status' has been updated in the status file.
with open(temp_json_file) as status_file:
bisect_contents = json.load(status_file)
self.assertEqual(
bisect_contents["jobs"][tryjob_index]["status"],
TryjobStatus.GOOD.value,
)
mock_find_tryjob_index.assert_called_once()
# Simulate the behavior of `FindTryjobIndex()` when the tryjob exists in the
# status file.
@mock.patch.object(update_tryjob_status, "FindTryjobIndex", return_value=0)
def testSuccessfullyUpdatedTryjobStatusToBad(self, mock_find_tryjob_index):
bisect_test_contents = {
"start": 369410,
"end": 369420,
"jobs": [{"rev": 369411, "status": "pending"}],
}
# Create a temporary .JSON file to simulate a .JSON file that has bisection
# contents.
with CreateTemporaryJsonFile() as temp_json_file:
with open(temp_json_file, "w") as f:
WritePrettyJsonFile(bisect_test_contents, f)
revision_to_update = 369411
# Index of the tryjob that is going to have its 'status' value updated.
tryjob_index = 0
custom_script = None
update_tryjob_status.UpdateTryjobStatus(
revision_to_update,
TryjobStatus.BAD,
temp_json_file,
custom_script,
)
# Verify that the tryjob's 'status' has been updated in the status file.
with open(temp_json_file) as status_file:
bisect_contents = json.load(status_file)
self.assertEqual(
bisect_contents["jobs"][tryjob_index]["status"],
TryjobStatus.BAD.value,
)
mock_find_tryjob_index.assert_called_once()
# Simulate the behavior of `FindTryjobIndex()` when the tryjob exists in the
# status file.
@mock.patch.object(update_tryjob_status, "FindTryjobIndex", return_value=0)
def testSuccessfullyUpdatedTryjobStatusToPending(
self, mock_find_tryjob_index
):
bisect_test_contents = {
"start": 369410,
"end": 369420,
"jobs": [{"rev": 369411, "status": "skip"}],
}
# Create a temporary .JSON file to simulate a .JSON file that has bisection
# contents.
with CreateTemporaryJsonFile() as temp_json_file:
with open(temp_json_file, "w") as f:
WritePrettyJsonFile(bisect_test_contents, f)
revision_to_update = 369411
# Index of the tryjob that is going to have its 'status' value updated.
tryjob_index = 0
custom_script = None
update_tryjob_status.UpdateTryjobStatus(
revision_to_update,
update_tryjob_status.TryjobStatus.SKIP,
temp_json_file,
custom_script,
)
# Verify that the tryjob's 'status' has been updated in the status file.
with open(temp_json_file) as status_file:
bisect_contents = json.load(status_file)
self.assertEqual(
bisect_contents["jobs"][tryjob_index]["status"],
update_tryjob_status.TryjobStatus.SKIP.value,
)
mock_find_tryjob_index.assert_called_once()
# Simulate the behavior of `FindTryjobIndex()` when the tryjob exists in the
# status file.
@mock.patch.object(update_tryjob_status, "FindTryjobIndex", return_value=0)
def testSuccessfullyUpdatedTryjobStatusToSkip(self, mock_find_tryjob_index):
bisect_test_contents = {
"start": 369410,
"end": 369420,
"jobs": [
{
"rev": 369411,
"status": "pending",
}
],
}
# Create a temporary .JSON file to simulate a .JSON file that has bisection
# contents.
with CreateTemporaryJsonFile() as temp_json_file:
with open(temp_json_file, "w") as f:
WritePrettyJsonFile(bisect_test_contents, f)
revision_to_update = 369411
# Index of the tryjob that is going to have its 'status' value updated.
tryjob_index = 0
custom_script = None
update_tryjob_status.UpdateTryjobStatus(
revision_to_update,
update_tryjob_status.TryjobStatus.PENDING,
temp_json_file,
custom_script,
)
# Verify that the tryjob's 'status' has been updated in the status file.
with open(temp_json_file) as status_file:
bisect_contents = json.load(status_file)
self.assertEqual(
bisect_contents["jobs"][tryjob_index]["status"],
update_tryjob_status.TryjobStatus.PENDING.value,
)
mock_find_tryjob_index.assert_called_once()
@mock.patch.object(update_tryjob_status, "FindTryjobIndex", return_value=0)
@mock.patch.object(
update_tryjob_status,
"GetCustomScriptResult",
return_value=TryjobStatus.SKIP.value,
)
def testUpdatedTryjobStatusToAutoPassedWithCustomScript(
self, mock_get_custom_script_result, mock_find_tryjob_index
):
bisect_test_contents = {
"start": 369410,
"end": 369420,
"jobs": [
{"rev": 369411, "status": "pending", "buildbucket_id": 1200}
],
}
# Create a temporary .JSON file to simulate a .JSON file that has bisection
# contents.
with CreateTemporaryJsonFile() as temp_json_file:
with open(temp_json_file, "w") as f:
WritePrettyJsonFile(bisect_test_contents, f)
revision_to_update = 369411
# Index of the tryjob that is going to have its 'status' value updated.
tryjob_index = 0
custom_script_path = "/abs/path/to/custom_script.py"
update_tryjob_status.UpdateTryjobStatus(
revision_to_update,
update_tryjob_status.TryjobStatus.CUSTOM_SCRIPT,
temp_json_file,
custom_script_path,
)
# Verify that the tryjob's 'status' has been updated in the status file.
with open(temp_json_file) as status_file:
bisect_contents = json.load(status_file)
self.assertEqual(
bisect_contents["jobs"][tryjob_index]["status"],
update_tryjob_status.TryjobStatus.SKIP.value,
)
mock_get_custom_script_result.assert_called_once()
mock_find_tryjob_index.assert_called_once()
# Simulate the behavior of `FindTryjobIndex()` when the tryjob exists in the
# status file.
@mock.patch.object(update_tryjob_status, "FindTryjobIndex", return_value=0)
def testSetStatusDoesNotExistWhenUpdatingTryjobStatus(
self, mock_find_tryjob_index
):
bisect_test_contents = {
"start": 369410,
"end": 369420,
"jobs": [
{"rev": 369411, "status": "pending", "buildbucket_id": 1200}
],
}
# Create a temporary .JSON file to simulate a .JSON file that has bisection
# contents.
with CreateTemporaryJsonFile() as temp_json_file:
with open(temp_json_file, "w") as f:
WritePrettyJsonFile(bisect_test_contents, f)
revision_to_update = 369411
nonexistent_update_status = "revert_status"
custom_script = None
# Verify the exception is raised when the `set_status` command line
# argument does not exist in the mapping.
with self.assertRaises(ValueError) as err:
update_tryjob_status.UpdateTryjobStatus(
revision_to_update,
nonexistent_update_status,
temp_json_file,
custom_script,
)
self.assertEqual(
str(err.exception),
'Invalid "set_status" option provided: revert_status',
)
mock_find_tryjob_index.assert_called_once()
if __name__ == "__main__":
unittest.main()