/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*      http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

syntax = "proto3";

package luci.resultdb.v1;

option go_package = "go.chromium.org/luci/resultdb/proto/v1;resultpb";
option java_package = "com.android.resultdb.proto";
option java_multiple_files = true;

// A key-value map describing one variant of a module.
//
// A module can be executed in different ways, for example on
// different OS, GPUs, with different compile options or runtime flags.
// A variant definition captures one such variant.
// A test case with a specific variant definition is called test variant.
//
// Guidelines for variant definition design:
// - This rule guides what keys MUST be present in the definition.
//   A single expected result of a given test variant is enough to consider it
//   passing (potentially flakily). If it is important to differentiate across
//   a certain dimension (e.g. whether web tests are executed with or without
//   site per process isolation), then there MUST be a key that captures the
//   dimension (e.g. a name from test_suites.pyl).
//   Otherwise, a pass in one variant will hide a failure of another one.
//
// - This rule guides what keys MUST NOT be present in the definition.
//   A change in the key-value set essentially resets the test result history.
//   For example, if GN args are among variant key-value pairs, then adding a
//   new GN arg changes the identity of the test variant and resets its history.
//
// In Chromium, variant keys are:
// - bucket: the LUCI bucket, e.g. "ci"
// - builder: the LUCI builder, e.g. "linux-rel"
// - test_suite: a name from
//   https://cs.chromium.org/chromium/src/testing/buildbot/test_suites.pyl
message Variant {
  // The definition of the variant.
  // Key and values must be valid StringPair keys and values, see their
  // constraints.
  //
  // These values must be the same for all tests in a given execution of
  // a module. Variants specific to a particular test case should be
  // encoded in the test method name (see TestIdentifier.method).
  //
  // The total size (as measured by proto.Size) must be less than 1,024 bytes.
  map<string, string> def = 1;
}

// A string key-value pair. Typically used for tagging, see Invocation.tags
message StringPair {
  // Regex: ^[a-z][a-z0-9_]*(/[a-z][a-z0-9_]*)*$
  // Max length: 64 bytes.
  string key = 1;

  // Must be valid UTF-8, Unicode Normalization Form C, printable characters
  // only.
  // Max length: 256 bytes.
  string value = 2;
}

// TestIdentifier represents the identity of a test in structured form.
// It includes information about the configuration the test was run in (e.g.
// build configuration, CPU architecture, OS).
//
// The structured test identifier represented in this proto corresponds to
// the flat test identifier with the following form:
// :{module_name}!{module_scheme}:{coarse_name}:{fine_name}#{case_name}
//
// and variant matching module_variant. (Note that {case_name} may in turn
// include multiple components separated by colons, see 'Extended depth
// hierarchies' under case_name below.)
//
// The precise algorithm for encoding a structured test ID to a flat
// test ID is an implementation detail of ResultDB and must not be relied
// upon by clients. Use the provided encode and decode functions in the
// pbutil library if you need to encode or decode them. BigQuery exports
// should include the structured form Test ID to avoid clients needing
// to decode IDs.
//
// Special treatment exists for legacy test identifiers, for which
// module_name = "legacy", module_scheme = "legacy", coarse_name = "",
// fine_name = "" and case_name = <legacy_id>. For these, the corresponding
// flat test ID is simply:
// {case_name}
//
// (no escaping applies to this form, although method starting with ':'
// is illegal for legacy test identifiers).
//
// See go/resultdb-test-hierarchy-design for more details.
//
// Limits: the encoded flat test ID (as per rules above) must be less than
// 512 bytes in UTF-8.
message TestIdentifier {
  // The name of the module being tested. A module is a set of tests which run
  // with the same test harness, and typically corresponds with a unit of
  // build, such as a bazel test target.
  //
  // For example, "CtsSdkExtensionsTestCases" or "chrome/test:blink_webkit_tests".
  //
  // Note: Modules are identified by the combination of module_name and scheme, not
  // only module_name. See `scheme` field below for more.
  //
  // Constraints:
  // - Limited to 300 bytes of valid, printable UTF-8. For the purposes of this
  //   definition, the unicode replacement character (U+FFFD) is not considered printable.
  //   See also length limits on `TestIdentifier` as a whole.
  // - All unicode must be in Normalization Form C.
  // - Use of spaces is allowed, but discouraged.
  string module_name = 1;

  // The scheme of the module, for example, "junit" or "gtest".
  //
  // Must match one of the schemes in the ResultDB service configuration (see
  // go/resultdb-schemes). Additional values can be configured as needed.
  //
  // Note: modules are identified by the combination of module_name, module_variant and
  // module_scheme.
  // Nonetheless, when presenting data on a frontend, it is acceptable to not show the scheme
  // as it exists primarily to allow customisation of the labels shown next to the coarse_name,
  // fine_name and case_name fields.
  // When uploading data it is STRONGLY DISCOURAGED to use different schemes with the same
  // module name as it may lead to ambiguous UI for users.
  //
  // Limited to ^[a-z][a-z0-9]{0,19}$.
  string module_scheme = 2;

  // Description of one specific way of running the tests in a module,
  // e.g. specific ABI (x64/x86/...), build flags and/or operating system.
  //
  // Variants identify the unique ways the module was run compared to every other way
  // the module is run by the LUCI project. As such, some variant key-value pairs
  // may be repeated for all modules in an invocation.
  //
  // N.B. Presently this is always the same as test_result.variant but semantics could
  // diverge in future if variants are introduced at other levels.
  Variant module_variant = 3;

  // Hash of the module variant.
  // hex(sha256(sorted(''.join('%s:%s\n' for k, v in module_variant.items()))))[:8].
  //
  // In the context of the TestResult record, this field is OUTPUT_ONLY and will be
  // automatically computed from the provided module_variant (do not set it yourself).
  // In the context of ResultDB Query RPCs accepting this type as a request filter,
  // this field may be specified as an alternative to module_variant.
  string module_variant_hash = 4;

  // Intermediate hierarchy - coarse name.
  //
  // For example "com.android.os.ext".
  //
  // A scheme dependent value used to organise the test into a coarse group of related tests,
  // such as a package or directory.
  // If the scheme does not define a coarse grouping, this must be blank.
  // If only one intermediate hierarchy level is used for a scheme, it is always the
  // fine hierarchy level.
  //
  // Constraints:
  // - Must match constraints set by the module's scheme.
  // - Limited to 300 bytes of printable UTF-8. For the purposes of this
  //   definition, the unicode replacement character (U+FFFD) is not considered printable.
  //   See also length limits on `TestIdentifier` as a whole.
  // - Must not start with one of the characters in [ !"#$%'()*+,] (i.e. U+0020 to U+002C).
  // - All unicode must be in Normalization Form C.
  // - Use of spaces is allowed, but discouraged.
  string coarse_name = 5;

  // Interemdiate hierarchy - fine name.
  //
  // For example "SdkExtensionsTest" or "WebDialogBrowserTest".
  //
  // A finer grouping within the above coarse grouping (if any), e.g. class or file.
  // If the scheme does not define a fine grouping, this must be blank.
  //
  // Constraints:
  // - Must match constraints set by the module's scheme.
  // - Limited to 300 bytes of printable UTF-8. For the purposes of this
  //   definition, the unicode replacement character (U+FFFD) is not considered printable.
  //   See also length limits on `TestIdentifier` as a whole.
  // - Must not start with one of the characters in [ !"#$%'()*+,] (i.e. U+0020 to U+002C).
  // - All unicode must be in Normalization Form C.
  // - Use of spaces is allowed, but discouraged.
  string fine_name = 6;

  // The identifier of test case within the above fine grouping.
  //
  // For example "testBadArgument" or "topLevelTest:with_context:does_something".
  //
  // This is the finest granularity component of the test identifier, and typically
  // refers to sub-file granularity unless no such granularity exists.
  //
  // Application guidance:
  // * Standard usage: E.g. GTest methods, JUnit methods, etc.
  //
  //   Put the test method name in this field, escaping any colons (':')
  //   and backlashes ('\') with a backslash '\'.
  //   If your tests have additional hierarchy in the test case name,
  //   consider the section on 'Extended depth hierarchies' below.
  //
  // * Class/package-level setup/teardown:
  //
  //   Use the special value "*fixture" when reporting the result of
  //   setup and teardown common to tests in the same fine_name.
  //
  // * Extended depth hierarchies:
  //
  //   When uploading a test case from an extended depth hierarchy, use the separator ':'
  //   to separate the components of the test case identifier (e.g. the describe blocks
  //   in a jest/mocha test). Text between the separators must have all occurrences
  //   of ':' and '\' escaped with a backslash ('\') to avoid other colons being interpreted
  //   as separators. Each component of such a hierarchical test case ID must not be empty.
  //   E.g. 'topLevelTest:' is not valid.
  //
  //   Rationale and Important Caveats
  //
  //   Most test hierarchy should be captured by the module, coarse and fine names.
  //   For example, the module typically captures the compilation unit, the coarse name
  //   the directory (e.g. package name) and the fine name the file (e.g. class name).
  //   This leaves the case name to capture sub-file granularity, which
  //   in many frameworks is simply the method name.
  //
  //   However, some frameworks have additional levels of test hierarchy within the
  //   "file" level. For example, Jest and Mocha allow tests to be nested within
  //   an arbitrary number of describe('subscope', func() { ... }) blocks. There may
  //   also be parameterisations of tests. For such cases, we offer a standardised
  //   way to express the additional hierarchy using colons (':') to separate components.
  //   UI may give special treatment to components so expressed in future.
  //
  //   Only consider uploading separate results for each test in such an additional
  //   depth hierarchy if all of the following hold:
  //   - Passes are reported for each test, not just failures.
  //   - Tests are independent (e.g. a assertion failure does not cause multiple
  //     failing results, e.g. failure of the child test does not also cause the
  //     parent to fail or vice-versa as is typical in many 'subtest' arrangements).
  //   - In case of retries, you are happy for each test passing individually at least
  //     once to allow CL submission. I.E. they do not all have to pass in the same run.
  //
  //   The above criteria are important to keep the test results view understandable in
  //   case of retries, and to protect the data model that downstream clients
  //   rely on (e.g. clients must report passes where they report failures).
  //
  //   If they cannot be met, do not upload separate tests results and prefer to upload one
  //   test result per top-level method. Currently Mocha and Jest tests, and parameterised
  //   GTest methods are the only cases where it is known these requirements can be met. Python
  //   unittests and go subtests do not meet the requirements due to falling foul of the
  //   independence requirement (and in case of python unittests, also the inability to
  //   report passes for subtests).
  //
  // Constraints:
  // - Must not start with one of the characters in [ !"#$%'()*+,] (i.e. U+0020 to U+002C),
  //   unless it is to write the value "*fixture".
  // - The use of the ':' character is reserved for denoting variable depth/extended depth
  //   hierarchies. If you do not intend this semantic, you must escape it using a backslash.
  //   (You must also escape all backslashes that are not denoting an escape sequence with
  //   a backslash.)
  // - When the case_name is broken up by ':', each such component must be non-empty.
  // - Limited to 512 bytes of printable UTF-8. For the purposes of this
  //   definition, the unicode replacement character (U+FFFD) is not considered printable.
  //   See also length limits on `TestIdentifier` as a whole.
  // - All unicode must be in Normalization Form C.
  // - Use of spaces is allowed, but discouraged.
  // - Legacy test identifiers (in module "legacy") are exempted from some of the
  //   above constraints to facilitate backwards compatibility but use of this quirk is
  //   discouraged.
  string case_name = 7;
}

// TestIdentifierPrefix represents a prefix of a structured test id.
//
// For the purposes of this message, all module_* fields are considered one
// logical field and must be set together.
//
// Compared to the TestIdentifier message, this message tracks which
// parts of the prefix is present. This is necessary, as:
//
// module_name: "//bazel_path:my_target"
// module_variant: [...]
// module_scheme: "myscheme"
// (module prefix)
//
// differs semantically from
//
// module_name: "//bazel_path:my_target"
// module_variant: [...]
// module_scheme: "myscheme"
// coarse_name: ""
// (coarse name prefix)
//
// As the former represents a module-level aggregate and the latter a coarse-name
// aggregate for coarse name "".
message TestIdentifierPrefix {
  // The aggregation level represented by the prefix.
  AggregationLevel level = 1;

  // The test identifier. This may have only some fields set, based on the
  // selected aggregation level.
  // If an AggregationLevel of Invocation is set, identifier must be unset or all its fields must be empty.
  // If an AggregationLevel of Module is set, all module fields must be set.
  // If an AggregationLevel of Coarse is set, all module fields and the coarse_name field must be set.
  // If an AggregationLevel of Fine is set, all module fields, and the coarse_name and fine_name fields must be set.
  // If an AggregationLevel of Case is set, all fields must be set.
  //
  // In case of coarse_name and fine_name, as empty ("") is a valid value, the fields
  // are taken to be set according to the set AggregationLevel.
  TestIdentifier id = 2;
}

// ModuleIdentifier represents the identity of a module.
message ModuleIdentifier {
  // The name of the module being tested. A module is a set of tests which run
  // with the same test harness, and typically corresponds with a unit of
  // build, such as a bazel test target.
  //
  // See TestIdentifier.module_name for more details.
  string module_name = 1;

  // The scheme of the module, for example, "junit" or "gtest".
  //
  // Must match one of the schemes in the ResultDB service configuration (see
  // go/resultdb-schemes). Additional values can be configured as needed.
  //
  // See TestIdentifier.module_scheme for more details.
  string module_scheme = 2;

  // Description of one specific way of running the tests in a module,
  // e.g. specific ABI (x64/x86/...), build flags and/or operating system.
  //
  // See TestIdentifier.module_variant for more details.
  Variant module_variant = 3;

  // Hash of the module variant.
  //
  // In the context of the WorkUnit record, this field is always OUTPUT_ONLY and will be
  // automatically computed from the provided module_variant (do not set it yourself).
  // In the context of ResultDB Query RPCs accepting this type as a request filter,
  // this field may be specified as an alternative to module_variant.
  //
  // See TestIdentifier.module_variant_hash for more details.
  string module_variant_hash = 4;
}

// GitilesCommit specifies the position of the gitiles commit an invocation
// ran against, in a repository's commit log. More specifically, a ref's commit
// log.
//
// It also specifies the host/project/ref combination that the commit
// exists in, to provide context.
message GitilesCommit {
  // The identity of the gitiles host, e.g. "chromium.googlesource.com".
  // Mandatory.
  string host = 1;

  // Repository name on the host, e.g. "chromium/src". Mandatory.
  string project = 2;

  // Commit ref, e.g. "refs/heads/main" from which the commit was fetched.
  // Not the branch name, use "refs/heads/branch"
  // Mandatory.
  string ref = 3;

  // Commit HEX SHA1. All lowercase. Mandatory.
  string commit_hash = 4;

  // Defines a total order of commits on the ref.
  // A positive, monotonically increasing integer. The recommended
  // way of obtaining this is by using the goto.google.com/git-numberer
  // Gerrit plugin. Other solutions can be used as well, so long
  // as the same scheme is used consistently for a ref.
  // Mandatory.
  int64 position = 5;
}

// A Gerrit patchset.
message GerritChange {
  // Gerrit hostname, e.g. "chromium-review.googlesource.com".
  string host = 1;
  // Gerrit project, e.g. "chromium/src".
  string project = 2;
  // Change number, e.g. 12345.
  int64 change = 3;
  // Patch set number, e.g. 1.
  int64 patchset = 4;
}

// Specifies the source code that was tested.
message Sources {
  // The base version of code sources checked out. Mandatory.
  // If necessary, we could add support for non-gitiles sources here in
  // future, using a oneof statement. E.g.
  // oneof system {
  //    GitilesCommit gitiles_commit = 1;
  //    SubversionRevision svn_revision = 4;
  //    ...
  // }
  GitilesCommit gitiles_commit = 1;

  // The changelist(s) which were applied upon the base version of sources
  // checked out. E.g. in commit queue tryjobs.
  //
  // At most 10 changelist(s) may be specified here. If there
  // are more, only include the first 10 and set is_dirty.
  repeated GerritChange changelists = 2;

  // Whether there were any changes made to the sources, not described above.
  // For example, a version of a dependency was upgraded before testing (e.g.
  // in an autoroller recipe).
  //
  // Cherry-picking a changelist on top of the base checkout is not considered
  // making the sources dirty as it is reported separately above.
  bool is_dirty = 3;
}

// Deprecated: Use GitilesCommit instead.
message CommitPosition {
  // The following fields identify a git repository and a ref within which the
  // numerical position below identifies a single commit.
  string host = 1;
  string project = 2;
  string ref = 3;

  // The numerical position of the commit in the log for the host/project/ref
  // above.
  int64 position = 4;
}

// Represents a reference in a source control system.
message SourceRef {
  // The source control system used.
  // Only gitiles is supported at this moment. If other systems need to be
  // supported in future (e.g. non-gitiles git, subversion, google storage
  // buckets), they can be added here
  oneof system {
    // A branch in gitiles repository.
    GitilesRef gitiles = 1;
  }
}

// Represents a branch in a gitiles repository.
message GitilesRef {
  // The gitiles host, e.g. "chromium.googlesource.com".
  string host = 1;

  // The project on the gitiles host, e.g. "chromium/src".
  string project = 2;

  // Commit ref, e.g. "refs/heads/main" from which the commit was fetched.
  // Not the branch name, use "refs/heads/branch"
  string ref = 3;
}

// AggregationLevel defines the levels that test verdicts can be
// aggregated to.
enum AggregationLevel {
  // Aggregation level not specified. Do not use.
  AGGREGATION_LEVEL_UNSPECIFIED = 0;
  // Aggregate to the invocation level. This means all test results
  // in an invocation.
  INVOCATION = 1;
  // Aggregate to the module level. This means all test results
  // sharing the same module_name, module_variant and module_scheme.
  MODULE = 2;
  // Aggregate to the coarse name level.
  // This means all test results sharing the same module and coarse_name.
  COARSE = 3;
  // Aggregate to the fine name level.
  // This means all test results sharing the same module, coarse_name,
  // and fine_name.
  FINE = 4;
  // Aggregate to the test case (test verdict) level.
  // This means all test results sharing the same test identifier.
  CASE = 5;
}
