| // Copyright 2022 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.runtime; |
| |
| import static com.google.common.truth.Truth.assertThat; |
| import static java.util.concurrent.TimeUnit.SECONDS; |
| import static org.mockito.Mockito.mock; |
| import static org.mockito.Mockito.when; |
| |
| import com.google.common.collect.ImmutableSet; |
| import com.google.devtools.build.lib.buildtool.BuildResult; |
| import com.google.devtools.build.lib.buildtool.ExecutionProgressReceiver; |
| import com.google.devtools.build.lib.buildtool.buildevent.BuildCompleteEvent; |
| import com.google.devtools.build.lib.buildtool.buildevent.ExecutionProgressReceiverAvailableEvent; |
| import com.google.devtools.build.lib.pkgcache.LoadingPhaseCompleteEvent; |
| import com.google.devtools.build.lib.runtime.SkymeldUiStateTracker.BuildStatus; |
| import com.google.devtools.build.lib.skyframe.ConfigurationPhaseStartedEvent; |
| import com.google.devtools.build.lib.skyframe.ConfiguredTargetProgressReceiver; |
| import com.google.devtools.build.lib.skyframe.LoadingPhaseStartedEvent; |
| import com.google.devtools.build.lib.skyframe.PackageProgressReceiver; |
| import com.google.devtools.build.lib.testutil.FoundationTestCase; |
| import com.google.devtools.build.lib.testutil.ManualClock; |
| import com.google.devtools.build.lib.util.DetailedExitCode; |
| import com.google.devtools.build.lib.util.Pair; |
| import com.google.devtools.build.lib.util.io.LoggingTerminalWriter; |
| import com.google.devtools.build.lib.util.io.PositionAwareAnsiTerminalWriter; |
| import java.io.IOException; |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| import org.junit.runners.JUnit4; |
| |
| /** Tests {@link SkymeldUiStateTracker}. */ |
| @RunWith(JUnit4.class) |
| public class SkymeldUiStateTrackerTest extends FoundationTestCase { |
| |
| @Test |
| public void buildStarted_stateChanges() { |
| ManualClock clock = new ManualClock(); |
| SkymeldUiStateTracker uiStateTracker = new SkymeldUiStateTracker(clock); |
| |
| assertThat(uiStateTracker.buildStatus).isEqualTo(BuildStatus.BUILD_NOT_STARTED); |
| uiStateTracker.buildStarted(); |
| assertThat(uiStateTracker.buildStatus).isEqualTo(BuildStatus.BUILD_STARTED); |
| } |
| |
| @Test |
| public void loadingStarted_stateChanges() { |
| ManualClock clock = new ManualClock(); |
| SkymeldUiStateTracker uiStateTracker = new SkymeldUiStateTracker(clock); |
| uiStateTracker.buildStatus = BuildStatus.BUILD_STARTED; |
| |
| uiStateTracker.loadingStarted( |
| new LoadingPhaseStartedEvent(mock(PackageProgressReceiver.class))); |
| |
| assertThat(uiStateTracker.buildStatus).isEqualTo(BuildStatus.TARGET_PATTERN_PARSING); |
| } |
| |
| @Test |
| public void loadingComplete_stateChanges() { |
| ManualClock clock = new ManualClock(); |
| SkymeldUiStateTracker uiStateTracker = new SkymeldUiStateTracker(clock); |
| uiStateTracker.buildStatus = BuildStatus.TARGET_PATTERN_PARSING; |
| |
| uiStateTracker.loadingComplete( |
| new LoadingPhaseCompleteEvent(ImmutableSet.of(), ImmutableSet.of())); |
| |
| assertThat(uiStateTracker.buildStatus).isEqualTo(BuildStatus.LOADING_COMPLETE); |
| } |
| |
| @Test |
| public void configurationStarted_stateChanges() { |
| ManualClock clock = new ManualClock(); |
| SkymeldUiStateTracker uiStateTracker = new SkymeldUiStateTracker(clock); |
| uiStateTracker.buildStatus = BuildStatus.LOADING_COMPLETE; |
| |
| uiStateTracker.configurationStarted( |
| new ConfigurationPhaseStartedEvent(mock(ConfiguredTargetProgressReceiver.class))); |
| |
| assertThat(uiStateTracker.buildStatus).isEqualTo(BuildStatus.CONFIGURATION); |
| } |
| |
| @Test |
| public void analysisAndExecution_stateChangesAndWriteProgressBar() throws IOException { |
| ManualClock clock = new ManualClock(); |
| SkymeldUiStateTracker uiStateTracker = new SkymeldUiStateTracker(clock); |
| String additionalMessage = "5 targets"; |
| uiStateTracker.buildStatus = BuildStatus.CONFIGURATION; |
| uiStateTracker.additionalMessage = additionalMessage; |
| |
| // First we need to set up the state tracker to already be analysing. |
| String loadingState = "42 packages loaded"; |
| String loadingActivity = "currently loading //src/foo/bar and 17 more"; |
| uiStateTracker.packageProgressReceiver = |
| mockPackageProgressReceiver(loadingState, loadingActivity); |
| |
| String configuredTargetProgressString = "5 targets configured"; |
| uiStateTracker.configuredTargetProgressReceiver = |
| mockConfiguredTargetProgressReceiver(configuredTargetProgressString); |
| |
| // Mock starting execution while configuring (before analysis complete). |
| ExecutionProgressReceiver executionProgressReceiver = new ExecutionProgressReceiver(0, null); |
| uiStateTracker.progressReceiverAvailable( |
| new ExecutionProgressReceiverAvailableEvent(executionProgressReceiver)); |
| |
| assertThat(uiStateTracker.buildStatus).isEqualTo(BuildStatus.ANALYSIS_AND_EXECUTION); |
| |
| LoggingTerminalWriter terminalWriter = new LoggingTerminalWriter(/*discardHighlight=*/ true); |
| uiStateTracker.writeProgressBar(terminalWriter); |
| String output = terminalWriter.getTranscript(); |
| // Should write analysis and execution information. |
| assertThat(output).contains("Analyzing"); |
| assertThat(output).contains(additionalMessage); |
| assertThat(output).contains(loadingState); |
| assertThat(output).contains(loadingActivity); |
| assertThat(output).contains(configuredTargetProgressString); |
| assertThat(output).contains("[0 / 0]"); |
| } |
| |
| @Test |
| public void executionFromAnalysisAndExecution_stateChanges() { |
| ManualClock clock = new ManualClock(); |
| SkymeldUiStateTracker uiStateTracker = new SkymeldUiStateTracker(clock); |
| uiStateTracker.buildStatus = BuildStatus.ANALYSIS_AND_EXECUTION; |
| |
| uiStateTracker.analysisComplete(); |
| |
| assertThat(uiStateTracker.buildStatus).isEqualTo(BuildStatus.EXECUTION); |
| } |
| |
| @Test |
| public void buildCompleted_stateChanges() { |
| ManualClock clock = new ManualClock(); |
| SkymeldUiStateTracker uiStateTracker = new SkymeldUiStateTracker(clock); |
| uiStateTracker.buildStatus = BuildStatus.EXECUTION; |
| |
| BuildResult buildResult = new BuildResult(clock.currentTimeMillis()); |
| buildResult.setDetailedExitCode(DetailedExitCode.success()); |
| clock.advanceMillis(SECONDS.toMillis(1)); |
| buildResult.setStopTime(clock.currentTimeMillis()); |
| uiStateTracker.buildComplete(new BuildCompleteEvent(buildResult)); |
| |
| assertThat(uiStateTracker.buildStatus).isEqualTo(BuildStatus.BUILD_COMPLETED); |
| } |
| |
| @Test |
| public void testWriteBaseProgress() throws IOException { |
| ManualClock clock = new ManualClock(); |
| SkymeldUiStateTracker uiStateTracker = new SkymeldUiStateTracker(clock); |
| String status = "status"; |
| String message = "hello"; |
| |
| uiStateTracker.buildStarted(); |
| uiStateTracker.ok = true; |
| LoggingTerminalWriter okTerminalWriter = new LoggingTerminalWriter(/*discardHighlight=*/ false); |
| uiStateTracker.writeBaseProgress( |
| status, message, new PositionAwareAnsiTerminalWriter(okTerminalWriter)); |
| assertOutputContainsBaseProgress( |
| okTerminalWriter.getTranscript(), status, message, /*ok=*/ true); |
| |
| uiStateTracker.ok = false; |
| LoggingTerminalWriter notOkTerminalWriter = |
| new LoggingTerminalWriter(/*discardHighlight=*/ false); |
| uiStateTracker.writeBaseProgress( |
| status, message, new PositionAwareAnsiTerminalWriter(notOkTerminalWriter)); |
| assertOutputContainsBaseProgress( |
| notOkTerminalWriter.getTranscript(), status, message, /*ok=*/ false); |
| } |
| |
| @Test |
| public void testWriteLoadingAnalysisPhaseProgress() throws IOException { |
| ManualClock clock = new ManualClock(); |
| SkymeldUiStateTracker uiStateTracker = new SkymeldUiStateTracker(clock); |
| uiStateTracker.ok = true; |
| String status = "status"; |
| String message = "message"; |
| String loadingState = "42 packages loaded"; |
| String loadingActivity = "currently loading //src/foo/bar and 17 more"; |
| String configuredTargetProgressString = "5 targets configured"; |
| |
| // Mock starting loading. |
| LoggingTerminalWriter terminalWriter = new LoggingTerminalWriter(/*discardHighlight=*/ false); |
| uiStateTracker.buildStatus = BuildStatus.TARGET_PATTERN_PARSING; |
| uiStateTracker.packageProgressReceiver = |
| mockPackageProgressReceiver(loadingState, loadingActivity); |
| |
| // Output should only contain loading-related output. |
| uiStateTracker.writeLoadingAnalysisPhaseProgress( |
| status, |
| message, |
| new PositionAwareAnsiTerminalWriter(terminalWriter), |
| /*shortVersion=*/ false); |
| String loadingOutput = terminalWriter.getTranscript(); |
| assertOutputContainsBaseProgress(loadingOutput, status, message, /*ok=*/ true); |
| assertThat(loadingOutput).contains("(" + loadingState + ")"); |
| assertThat(loadingOutput).contains(loadingActivity); |
| assertThat(loadingOutput).doesNotContain(configuredTargetProgressString); |
| |
| terminalWriter.reset(); |
| // When there is an empty message (only happens during target pattern parsing). |
| uiStateTracker.writeLoadingAnalysisPhaseProgress( |
| status, |
| /*message=*/ "", |
| new PositionAwareAnsiTerminalWriter(terminalWriter), |
| /*shortVersion=*/ false); |
| String emptyMessageLoadingOutput = terminalWriter.getTranscript(); |
| assertOutputContainsBaseProgress( |
| emptyMessageLoadingOutput, status, /*message=*/ "", /*ok=*/ true); |
| // The loading state should not be parenthesized. |
| assertThat(emptyMessageLoadingOutput).doesNotContain("(" + loadingState + ")"); |
| assertThat(emptyMessageLoadingOutput).contains(loadingState); |
| assertThat(emptyMessageLoadingOutput).contains(loadingActivity); |
| assertThat(emptyMessageLoadingOutput).doesNotContain(configuredTargetProgressString); |
| |
| terminalWriter.reset(); |
| // When writing as a short version. |
| uiStateTracker.writeLoadingAnalysisPhaseProgress( |
| status, |
| message, |
| new PositionAwareAnsiTerminalWriter(terminalWriter), |
| /*shortVersion=*/ true); |
| String shortVersionLoadingOutput = terminalWriter.getTranscript(); |
| assertOutputContainsBaseProgress(shortVersionLoadingOutput, status, message, /*ok=*/ true); |
| // Output should only contain the loading state but not the activity. |
| assertThat(shortVersionLoadingOutput).contains(loadingState); |
| assertThat(shortVersionLoadingOutput).doesNotContain(loadingActivity); |
| assertThat(emptyMessageLoadingOutput).doesNotContain(configuredTargetProgressString); |
| |
| terminalWriter.reset(); |
| // Mock starting configuration. |
| uiStateTracker.configuredTargetProgressReceiver = |
| mockConfiguredTargetProgressReceiver(configuredTargetProgressString); |
| uiStateTracker.buildStatus = BuildStatus.CONFIGURATION; |
| |
| // Output should contain both loading and analysis related output. |
| uiStateTracker.writeLoadingAnalysisPhaseProgress( |
| status, |
| message, |
| new PositionAwareAnsiTerminalWriter(terminalWriter), |
| /*shortVersion=*/ false); |
| String loadingAnalysisOutput = terminalWriter.getTranscript(); |
| assertOutputContainsBaseProgress(loadingAnalysisOutput, status, message, /*ok=*/ true); |
| assertThat(loadingAnalysisOutput).contains(loadingState); |
| assertThat(loadingAnalysisOutput).contains(loadingActivity); |
| assertThat(loadingAnalysisOutput).contains(configuredTargetProgressString); |
| } |
| |
| private static void assertOutputContainsBaseProgress( |
| String output, String status, String message, boolean ok) { |
| String okIndicator = ok ? LoggingTerminalWriter.OK : LoggingTerminalWriter.FAIL; |
| assertThat(output) |
| .contains(okIndicator + status + ":" + LoggingTerminalWriter.NORMAL + " " + message); |
| } |
| |
| private static PackageProgressReceiver mockPackageProgressReceiver( |
| String state, String activity) { |
| PackageProgressReceiver packageProgressReceiver = mock(PackageProgressReceiver.class); |
| when(packageProgressReceiver.progressState()).thenReturn(new Pair<>(state, activity)); |
| return packageProgressReceiver; |
| } |
| |
| private static ConfiguredTargetProgressReceiver mockConfiguredTargetProgressReceiver( |
| String progress) { |
| ConfiguredTargetProgressReceiver configuredTargetProgressReceiver = |
| mock(ConfiguredTargetProgressReceiver.class); |
| when(configuredTargetProgressReceiver.getProgressString()).thenReturn(progress); |
| return configuredTargetProgressReceiver; |
| } |
| } |