1
    2
    3
    4
    5
    6
    7
    8
    9
   10
   11
   12
   13
   14
   15
   16
   17
   18
   19
   20
   21
   22
   23
   24
   25
   26
   27
   28
   29
   30
   31
   32
   33
   34
   35
   36
   37
   38
   39
   40
   41
   42
   43
   44
   45
   46
   47
   48
   49
   50
   51
   52
   53
   54
   55
   56
   57
   58
   59
   60
   61
   62
   63
   64
   65
   66
   67
   68
   69
   70
   71
   72
   73
   74
   75
   76
   77
   78
   79
   80
   81
   82
   83
   84
   85
   86
   87
   88
   89
   90
   91
   92
   93
   94
   95
   96
   97
   98
   99
  100
  101
  102
  103
  104
  105
  106
  107
  108
  109
  110
  111
  112
  113
  114
  115
  116
  117
  118
  119
  120
  121
  122
  123
  124
  125
  126
  127
  128
  129
  130
  131
  132
  133
  134
  135
  136
  137
  138
  139
  140
  141
  142
  143
  144
  145
  146
  147
  148
  149
  150
  151
  152
  153
  154
  155
  156
  157
  158
  159
  160
  161
  162
  163
  164

base / test / metrics / histogram_enum_reader.cc [blame]

// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "base/test/metrics/histogram_enum_reader.h"

#include <optional>

#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/path_service.h"
#include "base/strings/string_number_conversions.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/libxml/chromium/xml_reader.h"

namespace base {
namespace {

// This is a helper function to the ReadEnumFromHistogramsXml().
// Extracts single enum (with integer values) from histograms.xml.
// Expects |reader| to point at given enum.
// Returns map { value => label } on success, and nullopt on failure.
std::optional<HistogramEnumEntryMap> ParseEnumFromHistogramsXml(
    const std::string& enum_name,
    XmlReader* reader) {
  int entries_index = -1;

  HistogramEnumEntryMap result;
  bool success = true;

  while (true) {
    const std::string node_name = reader->NodeName();
    if (node_name == "enum" && reader->IsClosingElement())
      break;

    if (node_name == "int") {
      ++entries_index;
      std::string value_str;
      std::string label;
      const bool has_value = reader->NodeAttribute("value", &value_str);
      const bool has_label = reader->NodeAttribute("label", &label);
      if (!has_value) {
        ADD_FAILURE() << "Bad " << enum_name << " enum entry (at index "
                      << entries_index << ", label='" << label
                      << "'): No 'value' attribute.";
        success = false;
      }
      if (!has_label) {
        ADD_FAILURE() << "Bad " << enum_name << " enum entry (at index "
                      << entries_index << ", value_str='" << value_str
                      << "'): No 'label' attribute.";
        success = false;
      }

      HistogramBase::Sample value;
      if (has_value && !StringToInt(value_str, &value)) {
        ADD_FAILURE() << "Bad " << enum_name << " enum entry (at index "
                      << entries_index << ", label='" << label
                      << "', value_str='" << value_str
                      << "'): 'value' attribute is not integer.";
        success = false;
      }
      if (result.count(value)) {
        ADD_FAILURE() << "Bad " << enum_name << " enum entry (at index "
                      << entries_index << ", label='" << label
                      << "', value_str='" << value_str
                      << "'): duplicate value '" << value_str
                      << "' found in enum. The previous one has label='"
                      << result[value] << "'.";
        success = false;
      }
      if (success)
        result[value] = label;
    }
    // All enum entries are on the same level, so it is enough to iterate
    // until possible.
    reader->Next();
  }
  if (success)
    return result;
  return std::nullopt;
}

}  // namespace

std::optional<HistogramEnumEntryMap> ReadEnumFromEnumsXml(
    const std::string& enum_name,
    const std::optional<std::string>& subdirectory) {
  FilePath src_root;
  if (!PathService::Get(DIR_SRC_TEST_DATA_ROOT, &src_root)) {
    ADD_FAILURE() << "Failed to get src root.";
    return std::nullopt;
  }

  base::FilePath enums_xml =
      src_root.AppendASCII("tools").AppendASCII("metrics").AppendASCII(
          "histograms");
  if (subdirectory) {
    enums_xml =
        enums_xml.AppendASCII("metadata").AppendASCII(subdirectory.value());
  }
  enums_xml = enums_xml.AppendASCII("enums.xml");

  if (!PathExists(enums_xml)) {
    ADD_FAILURE() << "enums.xml file does not exist.";
    return std::nullopt;
  }

  XmlReader enums_xml_reader;
  if (!enums_xml_reader.LoadFile(enums_xml.MaybeAsASCII())) {
    ADD_FAILURE() << "Failed to load enums.xml";
    return std::nullopt;
  }

  std::optional<HistogramEnumEntryMap> result;

  // Implement simple depth first search.
  while (true) {
    const std::string node_name = enums_xml_reader.NodeName();
    if (node_name == "enum") {
      std::string name;
      if (enums_xml_reader.NodeAttribute("name", &name) && name == enum_name) {
        if (result.has_value()) {
          ADD_FAILURE() << "Duplicate enum '" << enum_name
                        << "' found in enums.xml";
          return std::nullopt;
        }

        const bool got_into_enum = enums_xml_reader.Read();
        if (!got_into_enum) {
          ADD_FAILURE() << "Bad enum '" << enum_name
                        << "' (looks empty) found in enums.xml.";
          return std::nullopt;
        }

        result = ParseEnumFromHistogramsXml(enum_name, &enums_xml_reader);
        if (!result.has_value()) {
          ADD_FAILURE() << "Bad enum '" << enum_name
                        << "' found in histograms.xml (format error).";
          return std::nullopt;
        }
      }
    }
    // Go deeper if possible (stops at the closing tag of the deepest node).
    if (enums_xml_reader.Read())
      continue;

    // Try next node on the same level (skips closing tag).
    if (enums_xml_reader.Next())
      continue;

    // Go up until next node on the same level exists.
    while (enums_xml_reader.Depth() && !enums_xml_reader.SkipToElement()) {
    }

    // Reached top. histograms.xml consists of the single top level node
    // 'histogram-configuration', so this is the end.
    if (!enums_xml_reader.Depth())
      break;
  }
  return result;
}

}  // namespace base