#!/usr/bin/env vpython3
# Copyright 2020 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import itertools
import tempfile
from typing import Iterable, Set
import unittest
from unittest import mock
import six
from pyfakefs import fake_filesystem_unittest
from unexpected_passes_common import data_types
from unexpected_passes_common import result_output
from unexpected_passes_common import unittest_utils as uu
from blinkpy.w3c import buganizer
# Protected access is allowed for unittests.
# pylint: disable=protected-access
def CreateTextOutputPermutations(text: str, inputs: Iterable[str]) -> Set[str]:
"""Creates permutations of |text| filled with the contents of |inputs|.
Some output ordering is not guaranteed, so this acts as a way to generate
all possible outputs instead of manually listing them.
Args:
text: A string containing a single string field to format.
inputs: An iterable of strings to permute.
Returns:
A set of unique permutations of |text| filled with |inputs|. E.g. if |text|
is '1%s2' and |inputs| is ['a', 'b'], the return value will be
set(['1ab2', '1ba2']).
"""
permutations = set()
for p in itertools.permutations(inputs):
permutations.add(text % ''.join(p))
return permutations
class ConvertUnmatchedResultsToStringDictUnittest(unittest.TestCase):
def testEmptyResults(self) -> None:
"""Tests that providing empty results is a no-op."""
self.assertEqual(result_output._ConvertUnmatchedResultsToStringDict({}), {})
def testMinimalData(self) -> None:
"""Tests that everything functions when minimal data is provided."""
unmatched_results = {
'builder': [
data_types.Result('foo', [], 'Failure', 'step', 'build_id'),
],
}
expected_output = {
'foo': {
'builder': {
'step': [
'Got "Failure" on http://ci.chromium.org/b/build_id with '
'tags []',
],
},
},
}
output = result_output._ConvertUnmatchedResultsToStringDict(
unmatched_results)
self.assertEqual(output, expected_output)
def testRegularData(self) -> None:
"""Tests that everything functions when regular data is provided."""
unmatched_results = {
'builder': [
data_types.Result('foo', ['win', 'intel'], 'Failure', 'step_name',
'build_id')
],
}
# TODO(crbug.com/40177248): Hard-code the tag string once only Python 3 is
# supported.
expected_output = {
'foo': {
'builder': {
'step_name': [
'Got "Failure" on http://ci.chromium.org/b/build_id with '
'tags [%s]' % ' '.join(set(['win', 'intel'])),
]
}
}
}
output = result_output._ConvertUnmatchedResultsToStringDict(
unmatched_results)
self.assertEqual(output, expected_output)
class ConvertTestExpectationMapToStringDictUnittest(unittest.TestCase):
def testEmptyMap(self) -> None:
"""Tests that providing an empty map is a no-op."""
self.assertEqual(
result_output._ConvertTestExpectationMapToStringDict(
data_types.TestExpectationMap()), {})
def testSemiStaleMap(self) -> None:
"""Tests that everything functions when regular data is provided."""
expectation_map = data_types.TestExpectationMap({
'expectation_file':
data_types.ExpectationBuilderMap({
data_types.Expectation('foo/test', ['win', 'intel'], [
'RetryOnFailure'
]):
data_types.BuilderStepMap({
'builder':
data_types.StepBuildStatsMap({
'all_pass':
uu.CreateStatsWithPassFails(2, 0),
'all_fail':
uu.CreateStatsWithPassFails(0, 2),
'some_pass':
uu.CreateStatsWithPassFails(1, 1),
}),
}),
data_types.Expectation('foo/test', ['linux', 'intel'], [
'RetryOnFailure'
]):
data_types.BuilderStepMap({
'builder':
data_types.StepBuildStatsMap({
'all_pass':
uu.CreateStatsWithPassFails(2, 0),
}),
}),
data_types.Expectation('foo/test', ['mac', 'intel'], [
'RetryOnFailure'
]):
data_types.BuilderStepMap({
'builder':
data_types.StepBuildStatsMap({
'all_fail':
uu.CreateStatsWithPassFails(0, 2),
}),
}),
}),
})
# TODO(crbug.com/40177248): Remove the Python 2 version once we are fully
# switched to Python 3.
if six.PY2:
expected_output = {
'expectation_file': {
'foo/test': {
'"RetryOnFailure" expectation on "win intel"': {
'builder': {
'Fully passed in the following': [
'all_pass (2/2 passed)',
],
'Never passed in the following': [
'all_fail (0/2 passed)',
],
'Partially passed in the following': {
'some_pass (1/2 passed)': [
data_types.BuildLinkFromBuildId('build_id0'),
],
},
},
},
'"RetryOnFailure" expectation on "intel linux"': {
'builder': {
'Fully passed in the following': [
'all_pass (2/2 passed)',
],
},
},
'"RetryOnFailure" expectation on "mac intel"': {
'builder': {
'Never passed in the following': [
'all_fail (0/2 passed)',
],
},
},
},
},
}
else:
# Set ordering does not appear to be stable between test runs, as we can
# get either order of tags. So, generate them now instead of hard coding
# them.
linux_tags = ' '.join(set(['linux', 'intel']))
win_tags = ' '.join(set(['win', 'intel']))
mac_tags = ' '.join(set(['mac', 'intel']))
expected_output = {
'expectation_file': {
'foo/test': {
'"RetryOnFailure" expectation on "%s"' % linux_tags: {
'builder': {
'Fully passed in the following': [
'all_pass (2/2 passed)',
],
},
},
'"RetryOnFailure" expectation on "%s"' % win_tags: {
'builder': {
'Fully passed in the following': [
'all_pass (2/2 passed)',
],
'Partially passed in the following': {
'some_pass (1/2 passed)': [
data_types.BuildLinkFromBuildId('build_id0'),
],
},
'Never passed in the following': [
'all_fail (0/2 passed)',
],
},
},
'"RetryOnFailure" expectation on "%s"' % mac_tags: {
'builder': {
'Never passed in the following': [
'all_fail (0/2 passed)',
],
},
},
},
},
}
str_dict = result_output._ConvertTestExpectationMapToStringDict(
expectation_map)
self.assertEqual(str_dict, expected_output)
class ConvertUnusedExpectationsToStringDictUnittest(unittest.TestCase):
def testEmptyDict(self) -> None:
"""Tests that nothing blows up when given an empty dict."""
self.assertEqual(result_output._ConvertUnusedExpectationsToStringDict({}),
{})
def testBasic(self) -> None:
"""Basic functionality test."""
unused = {
'foo_file': [
data_types.Expectation('foo/test', ['win', 'nvidia'],
['Failure', 'Timeout']),
],
'bar_file': [
data_types.Expectation('bar/test', ['win'], ['Failure']),
data_types.Expectation('bar/test2', ['win'], ['RetryOnFailure'])
],
}
if six.PY2:
expected_output = {
'foo_file': [
'[ win nvidia ] foo/test [ Failure Timeout ]',
],
'bar_file': [
'[ win ] bar/test [ Failure ]',
'[ win ] bar/test2 [ RetryOnFailure ]',
],
}
else:
# Set ordering does not appear to be stable between test runs, as we can
# get either order of tags. So, generate them now instead of hard coding
# them.
tags = ' '.join(['nvidia', 'win'])
results = ' '.join(['Failure', 'Timeout'])
expected_output = {
'foo_file': [
'[ %s ] foo/test [ %s ]' % (tags, results),
],
'bar_file': [
'[ win ] bar/test [ Failure ]',
'[ win ] bar/test2 [ RetryOnFailure ]',
],
}
self.assertEqual(
result_output._ConvertUnusedExpectationsToStringDict(unused),
expected_output)
class HtmlToFileUnittest(fake_filesystem_unittest.TestCase):
def setUp(self) -> None:
self.setUpPyfakefs()
self._file_handle = tempfile.NamedTemporaryFile(delete=False, mode='w')
self._filepath = self._file_handle.name
def testLinkifyString(self) -> None:
"""Test for _LinkifyString()."""
self._file_handle.close()
s = 'a'
self.assertEqual(result_output._LinkifyString(s), 'a')
s = 'http://a'
self.assertEqual(result_output._LinkifyString(s),
'http://a')
s = 'link to http://a, click it'
self.assertEqual(result_output._LinkifyString(s),
'link to http://a, click it')
def testRecursiveHtmlToFileExpectationMap(self) -> None:
"""Tests _RecursiveHtmlToFile() with an expectation map as input."""
expectation_map = {
'foo': {
'"RetryOnFailure" expectation on "win intel"': {
'builder': {
'Fully passed in the following': [
'all_pass (2/2)',
],
'Never passed in the following': [
'all_fail (0/2)',
],
'Partially passed in the following': {
'some_pass (1/2)': [
data_types.BuildLinkFromBuildId('build_id0'),
],
},
},
},
},
}
result_output._RecursiveHtmlToFile(expectation_map, self._file_handle)
self._file_handle.close()
# pylint: disable=line-too-long
# TODO(crbug.com/40177248): Remove the Python 2 version once we've fully
# switched to Python 3.
if six.PY2:
expected_output = """\