| // 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.cmdline.RepositoryMapping; | |
| 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(), RepositoryMapping.ALWAYS_FALLBACK)); | |
| 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()); | |
| var unused = 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; | |
| } | |
| } |