| // Copyright 2014 The Bazel Authors. 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.cmdline.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. |
| * |
| * <p> The information is grouped into following structures: |
| * <ul> |
| * <li> {@link #commandInfo} contain information about the build known when it starts but before |
| * anything is actually compiled/run |
| * <li> {@link #testCases} contain detailed information about each test case ran, for now they're |
| * |
| * </ul> |
| */ |
| public class WebStatusBuildLog { |
| private Gson gson = new Gson(); |
| private boolean complete = false; |
| private static final Logger LOG = |
| Logger.getLogger(WebStatusEventCollector.class.getCanonicalName()); |
| private Map<String, JsonElement> commandInfo = new HashMap<>(); |
| private Map<String, JsonObject> testCases = new HashMap<>(); |
| private long startTime; |
| private ImmutableList<String> 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<String> targets) { |
| this.targetList = ImmutableList.copyOf(targets); |
| } |
| |
| public void finish() { |
| commandInfo = ImmutableMap.copyOf(commandInfo); |
| complete = true; |
| } |
| |
| public Map<String, JsonElement> getCommandInfo() { |
| return commandInfo; |
| } |
| |
| public ImmutableMap<String, JsonObject> 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<String> 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<Long> 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; |
| } |
| } |