blob: b8ab98be712bce2d0a4192047966e4efd4bb7684 [file] [log] [blame]
/**
* Copyright 2019 Google LLC
*
* 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.
*/
import {SimpleChange} from '@angular/core';
import {ComponentFixture, fakeAsync, TestBed, tick} from '@angular/core/testing';
import {NoopAnimationsModule} from '@angular/platform-browser/animations';
import {RouterTestingModule} from '@angular/router/testing';
import {of, throwError} from 'rxjs';
import {APP_DATA, AppData} from '../services/app_data';
import {FileService} from '../services/file_service';
import {MttClient} from '../services/mtt_client';
import {TestRunOutput} from '../services/mtt_models';
import {CommandAttempt, CommandState} from '../services/tfc_models';
import {newMockRequest, newMockTest, newMockTestRun, newMockTestRunOutput} from '../testing/mtt_mocks';
import {MAX_CONSOLE_LENGTH, POLL_INTERVAL, TestRunConsole} from './test_run_console';
import {TestRunsModule} from './test_runs_module';
/** Constructs an active or inactive command attempt. */
function newCommandAttempt(active: boolean, id = 'attempt_id'): CommandAttempt {
return {
request_id: 'request_id',
command_id: 'command_id',
attempt_id: id,
state: active ? CommandState.RUNNING : CommandState.COMPLETED,
hostname: 'hostname',
};
}
describe('TestRunConsole', () => {
let output: TestRunOutput;
let mtt: jasmine.SpyObj<MttClient>;
let fixture: ComponentFixture<TestRunConsole>;
let console: TestRunConsole;
const appData: AppData = {};
beforeEach(() => {
// Set default test run output.
mtt = jasmine.createSpyObj('mtt', ['getTestRunOutput']);
output = newMockTestRunOutput(['hello', 'world'], 123);
mtt.getTestRunOutput.and.returnValue(of(output));
TestBed.configureTestingModule({
imports: [TestRunsModule, NoopAnimationsModule, RouterTestingModule],
providers: [
{provide: MttClient, useValue: mtt},
{provide: FileService, useValue: {}},
{provide: APP_DATA, useValue: appData},
],
});
// Configure component.
fixture = TestBed.createComponent(TestRunConsole);
console = fixture.componentInstance;
console.testRun = newMockTestRun(newMockTest(), 'test_run_id');
fixture.detectChanges();
// Spy on the polling methods.
spyOn(console, 'clearConsole').and.callThrough();
spyOn(console, 'resetPolling').and.callThrough();
spyOn(console, 'stopPolling').and.callThrough();
});
afterEach(() => {
fixture.destroy();
});
it('stops polling when disabled', () => {
console.disabled = true;
console.ngOnChanges({disabled: new SimpleChange(false, true, true)});
expect(console.clearConsole).toHaveBeenCalled();
expect(console.stopPolling).toHaveBeenCalled();
});
it('starts polling when enabled', () => {
const attempt = newCommandAttempt(false);
console.selectedAttempt = attempt;
console.request = newMockRequest([], [attempt]);
console.ngOnChanges({disabled: new SimpleChange(true, false, true)});
// No changes but will enabling will force polling to restart.
expect(console.resetPolling).toHaveBeenCalled();
});
it('starts polling when request loaded', () => {
const first = newCommandAttempt(false, 'first');
const second = newCommandAttempt(false, 'second');
console.request = newMockRequest([], [first, second]);
console.ngOnChanges(
{request: new SimpleChange(undefined, console.request, true)});
// Selects latest attempt and starts polling.
expect(console.selectedAttempt).toEqual(second);
expect(console.clearConsole).toHaveBeenCalled();
expect(console.resetPolling).toHaveBeenCalled();
});
it('can locate selected attempt', () => {
const active = newCommandAttempt(true, 'valid');
const final = newCommandAttempt(false, 'valid');
const other = newCommandAttempt(false, 'invalid');
console.selectedAttempt = active;
console.request = newMockRequest([], [final, other]);
console.ngOnChanges(
{request: new SimpleChange(undefined, console.request, true)});
// Finds the right attempt without restarting polling.
expect(console.selectedAttempt).toEqual(final);
expect(console.clearConsole).not.toHaveBeenCalled();
expect(console.resetPolling).not.toHaveBeenCalled();
});
it('cannot load without attempt', () => {
console.selectedAttempt = undefined;
console.resetPolling();
expect(mtt.getTestRunOutput).not.toHaveBeenCalled();
expect(console.output).toEqual([]);
expect(console.offset).toBeUndefined();
});
it('can load active console', fakeAsync(() => {
console.selectedAttempt = newCommandAttempt(true);
console.resetPolling();
expect(mtt.getTestRunOutput)
.toHaveBeenCalledWith(
'test_run_id', 'attempt_id', 'logs/host_log.txt', undefined);
expect(console.output).toEqual(['world']); // First line skipped.
expect(console.offset).toEqual(output.offset + output.length - 1);
tick(POLL_INTERVAL);
expect(console.output).toEqual(['world', 'world']); // Auto-updates.
console.stopPolling();
}));
it('can load inactive console', fakeAsync(() => {
console.selectedAttempt = newCommandAttempt(false);
console.resetPolling();
expect(mtt.getTestRunOutput)
.toHaveBeenCalledWith(
'test_run_id', 'attempt_id', 'tool-logs/host_log.txt',
undefined);
expect(console.output).toEqual(['world']); // First line skipped.
expect(console.offset).toEqual(output.offset + output.length - 1);
tick(POLL_INTERVAL);
expect(console.output).toEqual(['world']); // Doesn't auto-update.
console.stopPolling();
}));
it('can change log type', () => {
console.selectedAttempt = newCommandAttempt(false);
console.selectedType = 'stdout.txt';
console.resetPolling();
expect(mtt.getTestRunOutput)
.toHaveBeenCalledWith(
'test_run_id', 'attempt_id', 'tool-logs/stdout.txt', undefined);
});
it('can handle errors', fakeAsync(() => {
// 2nd request will fail
mtt.getTestRunOutput.and.returnValues(
of(output), throwError('expected'), of(output));
console.selectedAttempt = newCommandAttempt(true);
console.resetPolling();
tick(POLL_INTERVAL);
expect(console.output).toEqual([]); // Output cleared.
expect(console.offset).toBeUndefined(); // Offset reset.
tick(POLL_INTERVAL);
expect(console.output).toEqual(['world']); // Continues to auto-update.
console.stopPolling();
}));
it('can truncate console', () => {
// Fetch twice as much content as the maximum.
const length = 2 * MAX_CONSOLE_LENGTH;
const content = Array.from<string>({length}).fill('test');
mtt.getTestRunOutput.and.returnValue(of(newMockTestRunOutput(content)));
console.selectedAttempt = newCommandAttempt(false);
console.resetPolling();
// Content was truncated.
expect(console.output.length).toEqual(MAX_CONSOLE_LENGTH);
});
it('can restart polling', fakeAsync(() => {
console.selectedAttempt = newCommandAttempt(true);
console.resetPolling();
expect(mtt.getTestRunOutput).toHaveBeenCalledTimes(1);
// Delay not reached, but restarting the polling will fetch data.
tick(POLL_INTERVAL / 2);
expect(mtt.getTestRunOutput).toHaveBeenCalledTimes(1);
console.resetPolling();
expect(mtt.getTestRunOutput).toHaveBeenCalledTimes(2);
// Poll delay has been reset.
tick(POLL_INTERVAL / 2);
expect(mtt.getTestRunOutput).toHaveBeenCalledTimes(2);
tick(POLL_INTERVAL / 2);
expect(mtt.getTestRunOutput).toHaveBeenCalledTimes(3);
console.stopPolling();
}));
});