// Copyright 2014 Google Inc. All rights reserved. // // 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. package com.google.devtools.build.lib.webstatusserver; import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.devtools.build.lib.syntax.Label; import com.google.devtools.build.lib.view.test.TestStatus.BlazeTestStatus; import com.google.devtools.build.lib.view.test.TestStatus.TestCase; import com.google.gson.Gson; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.UUID; import java.util.logging.Logger; /** * Stores information about one build command. The data is stored in JSON so that it can be * can be easily fed to frontend. * *

The information is grouped into following structures: *

*/ public class WebStatusBuildLog { private Gson gson = new Gson(); private boolean complete = false; private static final Logger LOG = Logger.getLogger(WebStatusEventCollector.class.getCanonicalName()); private Map commandInfo = new HashMap<>(); private Map testCases = new HashMap<>(); private long startTime; private ImmutableList targetList; private UUID commandId; public WebStatusBuildLog(UUID commandId) { this.commandId = commandId; } public WebStatusBuildLog addInfo(String key, Object value) { commandInfo.put(key, gson.toJsonTree(value)); return this; } public void addStartTime(long startTime) { this.startTime = startTime; } public void addTargetList(List targets) { this.targetList = ImmutableList.copyOf(targets); } public void finish() { commandInfo = ImmutableMap.copyOf(commandInfo); complete = true; } public Map getCommandInfo() { return commandInfo; } public ImmutableMap getTestCases() { // TODO(bazel-team): not really immutable, since one can do addProperty on // values (unfortunately gson doesn't support immutable JsonObjects) return ImmutableMap.copyOf(testCases); } public boolean finished() { return complete; } public List getTargetList() { return targetList; } public long getStartTime() { return startTime; } public void addTestTarget(Label label) { String targetName = label.toShorthandString(); if (!testCases.containsKey(targetName)) { JsonObject summary = createTestCaseEmptyJsonNode(targetName); summary.addProperty("finished", false); summary.addProperty("status", "started"); testCases.put(targetName, summary); } else { // TODO(bazel-team): figure out if there are any situations it can happen } } public void addTestSummary(Label label, BlazeTestStatus status, List testTimes, boolean isCached) { JsonObject testCase = testCases.get(label.toShorthandString()); testCase.addProperty("status", status.toString()); testCase.add("times", gson.toJsonTree(testTimes)); testCase.addProperty("cached", isCached); testCase.addProperty("finished", true); } public void addTargetBuilt(Label label, boolean success) { if (testCases.containsKey(label.toShorthandString())) { if (success) { testCases.get(label.toShorthandString()).addProperty("status", "built"); } else { testCases.get(label.toShorthandString()).addProperty("status", "build failure"); } } else { LOG.info("Unhandled target: " + label); } } @VisibleForTesting static JsonObject createTestCaseEmptyJsonNode(String fullName) { JsonObject currentNode = new JsonObject(); currentNode.addProperty("fullName", fullName); currentNode.addProperty("name", ""); currentNode.addProperty("className", ""); currentNode.add("results", new JsonObject()); currentNode.add("times", new JsonObject()); currentNode.add("children", new JsonObject()); currentNode.add("failures", new JsonObject()); currentNode.add("errors", new JsonObject()); return currentNode; } private static JsonObject createTestCaseEmptyJsonNode(String fullName, TestCase testCase) { JsonObject currentNode = createTestCaseEmptyJsonNode(fullName); currentNode.addProperty("name", testCase.getName()); currentNode.addProperty("className", testCase.getClassName()); return currentNode; } private JsonObject mergeTestCases(JsonObject currentNode, String fullName, TestCase testCase, int shardNumber) { if (currentNode == null) { currentNode = createTestCaseEmptyJsonNode(fullName, testCase); } if (testCase.getRun()) { JsonObject results = (JsonObject) currentNode.get("results"); JsonObject times = (JsonObject) currentNode.get("times"); if (testCase.hasResult()) { results.addProperty(Integer.toString(shardNumber), testCase.getResult()); } if (testCase.hasStatus()) { results.addProperty(Integer.toString(shardNumber), testCase.getStatus().toString()); } if (testCase.hasRunDurationMillis()) { times.addProperty(Integer.toString(shardNumber), testCase.getRunDurationMillis()); } } JsonObject children = (JsonObject) currentNode.get("children"); for (TestCase child : testCase.getChildList()) { String fullChildName = child.getClassName() + "." + child.getName(); JsonObject childNode = mergeTestCases((JsonObject) children.get(fullChildName), fullChildName, child, shardNumber); if (!children.has(fullChildName)) { children.add(fullChildName, childNode); } } return currentNode; } public void addTestResult(Label label, TestCase testCase, int shardNumber) { String testResultFullName = label.toShorthandString(); if (!testCases.containsKey(testResultFullName)) { testCases.put(testResultFullName, createTestCaseEmptyJsonNode(testResultFullName, testCase)); } mergeTestCases(testCases.get(testResultFullName), testResultFullName, testCase, shardNumber); } public UUID getCommandId() { return commandId; } }