blob: 78c246345fa72c2a2cdc396e53c5e7cbc87f3934 [file] [log] [blame]
# Copyright 2023 The LUCI Authors. All rights reserved.
# Use of this source code is governed under the Apache License, Version 2.0
# that can be found in the LICENSE file.
from google.protobuf import json_format
from google.protobuf import timestamp_pb2
from google.protobuf.message import Message
from recipe_engine import recipe_test_api
class LuciAnalysisTestApi(recipe_test_api.RecipeTestApi):
def construct_recent_verdicts(self, expected_count, unexpected_count):
verdicts = []
for i in range(expected_count):
verdicts.append({
'ingested_invocation_id': 'invocation_id_' + str(i),
'hasUnexpectedRuns': False,
})
for i in range(unexpected_count):
verdicts.append({
'ingested_invocation_id': 'invocation_id_' + str(i * 10),
'hasUnexpectedRuns': True,
})
return verdicts
def construct_flaky_verdict_examples(self, example_times):
verdict_examples = []
if example_times:
for example_time in example_times:
verdict_examples.append({
'partitionTime':
timestamp_pb2.Timestamp(seconds=example_time).ToJsonString(),
})
return verdict_examples
def generate_analysis(self,
test_id,
expected_count=10,
unexpected_count=0,
flaky_verdict_counts=(0, 0),
examples_times=None):
interval_stats = [{
'intervalAge': i + 1,
'totalRunExpectedVerdicts': 300,
'totalRunUnexpectedVerdicts': 1,
'totalRunFlakyVerdicts': count,
} for i, count in enumerate(flaky_verdict_counts)]
return {
'testId':
test_id,
'variantHash':
'fake_variant_hash',
'intervalStats':
interval_stats,
'recentVerdicts':
self.construct_recent_verdicts(
expected_count=expected_count,
unexpected_count=unexpected_count,
),
'runFlakyVerdictExamples':
self.construct_flaky_verdict_examples(examples_times)
}
@recipe_test_api.mod_test_data
@staticmethod
def query_failure_rate_results(analysis_list):
"""Returns a test_id -> analysis dict to be used by the luci_analysis module
analysis_list: List of analysis dicts created from generate_analysis()
Returns: Dict
"""
return {analysis['testId']: analysis for analysis in analysis_list}
def query_test_history(self,
response,
test_id,
parent_step_name=None,
step_iteration=1):
"""Emulates query_test_history() return value.
Args:
response: (luci.analysis.v1.test_history.QueryTestHistoryResponse) the
response to simulate.
test_id: (str) Test ID to query.
parent_step_name: (str) The parent step name under which
query_test_history is nested in, if any.
step_iteration: (int) Used when the API is called multiple times for a
same test_id.
"""
parent_step_prefix = ''
if parent_step_name:
parent_step_prefix = ('%s.' % parent_step_name)
step_suffix = ''
if step_iteration > 1:
step_suffix = ' (%d)' % step_iteration
step_name = ('%sTest history query rpc call for %s%s' %
(parent_step_prefix, test_id, step_suffix))
return self.step_data(
step_name,
self.m.json.output_stream(json_format.MessageToDict(response)))
def query_variants(self,
response,
test_id,
parent_step_name=None,
step_iteration=1):
"""Emulates query_variants() return value.
Args:
response (luci.analysis.v1.test_history.QueryVariantsResponse): the
response to simulate.
test_id (str): Test ID to query.
parent_step_name (str): The parent step name under which step is nested
in, if any.
step_iteration: (int) Used when the API is called multiple times for a
same test_id.
"""
parent_step_prefix = ''
if parent_step_name:
parent_step_prefix = ('%s.' % parent_step_name)
step_suffix = ''
if step_iteration > 1:
step_suffix = ' (%d)' % step_iteration
step_name = ('%sTest history query_variants rpc call for %s%s' %
(parent_step_prefix, test_id, step_suffix))
return self.step_data(
step_name,
self.m.json.output_stream(json_format.MessageToDict(response)))
def lookup_bug(self,
rules,
bug_id,
system='monorail',
parent_step_name=None,
step_iteration=1):
"""Emulates lookup_bug() return value.
Args:
rules (list of rules): Format: projects/{project}/rules/{rule_id}
bug_id (str): Id is the bug tracking system-specific identity of the bug.
For monorail, the scheme is {project}/{numeric_id}, for buganizer the
scheme is {numeric_id}.
system (str): System is the bug tracking system of the bug. This is either
"monorail" or "buganizer". Defaults to monorail.
parent_step_name (str): The parent step name under which step is nested
in, if any.
step_iteration: (int) Used when the API is called multiple times for a
same test_id.
"""
parent_step_prefix = ('%s.' % parent_step_name) if parent_step_name else ''
step_suffix = (' (%d)' % step_iteration) if step_iteration > 1 else ''
step_name = ('%sLookup Bug %s:%s%s' %
(parent_step_prefix, system, bug_id, step_suffix))
return self.step_data(step_name,
self.m.json.output_stream({'rules': rules}))
def query_cluster_failures(self,
failures,
cluster_name,
parent_step_name=None,
step_iteration=1):
"""Emulates query_cluster_failures() return value.
Args:
failures (list of DistinctClusterFailure): https://bit.ly/DistinctClusterFailure
cluster_name (str): The resource name of the cluster to retrieve.
Format: projects/{project}/clusters/{cluster_algorithm}/{cluster_id}
parent_step_name (str): The parent step name under which step is nested
in, if any.
step_iteration: (int) Used when the API is called multiple times for a
same test_id.
"""
parent_step_prefix = ('%s.' % parent_step_name) if parent_step_name else ''
step_suffix = (' (%d)' % step_iteration) if step_iteration > 1 else ''
step_name = ('%sQuery Cluster Failure %s%s' %
(parent_step_prefix, cluster_name, step_suffix))
return self.step_data(
step_name,
self.m.json.output_stream({
'failures': [
json_format.MessageToDict(x) if isinstance(x, Message) else x
for x in failures
]
}))
@staticmethod
def query_stability_example_input():
return [{
"testId": "tast.lockscreen.CloseLid.fieldtrial_testing_config_on",
"variant": {
"def": {
"board": "hana",
"build_target": "hana"
}
},
"sources": {
"changelists": [{
"change": "5138795",
"host": "chromium-review.googlesource.com",
"patchset": "2",
"project": "chromiumos/overlays/chromiumos-overlay"
}],
"gitilesCommit": {
"host": "chrome-internal.googlesource.com",
"project": "chromeos/manifest-internal",
"ref": "refs/heads/snapshot",
"commitHash": "47ccd8e3e33bcab3f56963c765c67deec7775292",
"position": "92976"
}
}
}]
@staticmethod
def query_stability_example_output():
return {
"testVariants": [{
"testId": "tast.lockscreen.CloseLid.fieldtrial_testing_config_on",
"variant": {
"def": {
"board": "hana",
"build_target": "hana"
}
},
"failureRate": {
"isMet":
True,
"unexpectedTestRuns":
10,
"consecutiveUnexpectedTestRuns":
10,
"recentVerdicts": [{
"position": "93211",
"invocations": ["build-8759159713660408161"],
"unexpectedRuns": 2,
"totalRuns": 2
}, {
"position": "93210",
"invocations": ["build-8759161637520826273"],
"unexpectedRuns": 2,
"totalRuns": 2
}, {
"position": "93208",
"invocations": ["build-8759165807629055393"],
"unexpectedRuns": 2,
"totalRuns": 2
}, {
"position": "93207",
"invocations": ["build-8759167441766272785"],
"unexpectedRuns": 2,
"totalRuns": 2
}, {
"position": "93206",
"invocations": ["build-8759169052502629345"],
"unexpectedRuns": 2,
"totalRuns": 2
}]
},
"flakeRate": {
"totalVerdicts": 134,
"startPosition": "93206",
"endPosition": "93552"
}
}],
"criteria": {
"failureRate": {
"failureThreshold": 6,
"consecutiveFailureThreshold": 3
},
"flakeRate": {
"minWindow": 100,
"flakeThreshold": 2,
"flakeRateThreshold": 0.01
}
}
}