Update Auto-Qual WebUI to allow firmware version specification Testing instructions: 1. Test in conjunction with RPC and proto changes. 2. Go to auto-qual > schedule test effort. 3. Schedule an avs/firmware testplan run and specify firmware versions for the test. 4. Verify the firmware versions show up correctly on the scheduling backend. Change-Id: I716c2287e9458003fed2c54c194b9aba14653bb2 Bug: b:559775353 Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/satlab/+/8619772 Reviewed-by: Rafał Gaweł <rafalgawel@google.com> Tested-by: Kuba Birecki <kbirecki@google.com> Auto-Submit: Kuba Birecki <kbirecki@google.com> Commit-Queue: Kuba Birecki <kbirecki@google.com>
diff --git a/src/satlab-ui/src/app/protos/satlabrpc.proto b/src/satlab-ui/src/app/protos/satlabrpc.proto index 87b68bb..997f768 100644 --- a/src/satlab-ui/src/app/protos/satlabrpc.proto +++ b/src/satlab-ui/src/app/protos/satlabrpc.proto
@@ -1323,7 +1323,7 @@ string id = 1; } -// NEXT_TAG = 13 +// NEXT_TAG = 14 message TestEffort { // NEXT_TAG = 10 @@ -1403,6 +1403,68 @@ PoolType type = 2; } + // NEXT_TAG = 4 + // Represents the version of a firmware target. + message FirmwareTestEffortVersion { + // Required. The major version of the firmware. + int32 major_version = 1; + + // Required. The minor version of the firmware. + int32 minor_version = 2; + + // Required. The patch number of the firmware. + int32 patch_number = 3; + } + + // NEXT_TAG = 4 + // Represents the version of a GSC firmware target. + message GscTestEffortVersion { + // Required. The major version of the GSC firmware. + int32 major_version = 1; + + // Required. The minor version of the GSC firmware. + int32 minor_version = 2; + + // Required. The generation of the GSC firmware. + int32 generation = 3; + } + + // NEXT_TAG = 5 + // Specifies the firmware target and its version. + message FirmwareTarget { + oneof fw { + // Embedded Controller firmware version. + FirmwareTestEffortVersion ec = 1; + + // Application Processor firmware version. + FirmwareTestEffortVersion ap = 2; + + // Google Security Chip firmware version. + GscTestEffortVersion gsc = 3; + + // Fingerprint Microcontroller firmware version. + FirmwareTestEffortVersion fpmcu = 4; + } + } + + // NEXT_TAG = 3 + // The type of firmware (read-only or read-write). + enum FirmwareType { + FIRMWARE_TYPE_UNSPECIFIED = 0; + FIRMWARE_TYPE_READ_ONLY = 1; + FIRMWARE_TYPE_READ_WRITE = 2; + } + + // NEXT_TAG = 3 + // Configuration for a firmware target. + message FirmwareConfig { + // Required. The firmware target and version. + FirmwareTarget firmware_target = 1; + + // Required. The firmware types (e.g. FIRMWARE_TYPE_READ_ONLY, FIRMWARE_TYPE_READ_WRITE). + repeated FirmwareType firmware_types = 2; + } + // Output only. The ID of the test effort. string id = 1; @@ -1444,6 +1506,9 @@ // Option to make boot tests optional. bool skip_boot_prerequisite = 12; + + // Optional. Firmware configurations to test against. + repeated FirmwareConfig firmware_configs = 13; } // NEXT_TAG = 2
diff --git a/src/satlab-ui/src/app/run_suite/android/auto-qual/auto-qual.component.html b/src/satlab-ui/src/app/run_suite/android/auto-qual/auto-qual.component.html index 62561e2..ae810b4 100644 --- a/src/satlab-ui/src/app/run_suite/android/auto-qual/auto-qual.component.html +++ b/src/satlab-ui/src/app/run_suite/android/auto-qual/auto-qual.component.html
@@ -209,6 +209,114 @@ (isLoadingChanged)="onLoadingChanged($event)" ></app-android-build-picker> + <div class="my-2"> + <mat-slide-toggle + (change)="onFirmwareVersionEnabledChanged($event.checked)" + [checked]="isFirmwareVersionEnabledSignal()" + [disabled]="isScheduleRunLoading().show" + > + Add Firmware Versions + </mat-slide-toggle> + </div> + + <div + *ngIf="isFirmwareVersionEnabledSignal()" + class="flex flex-col gap-3 my-2 p-4 border border-gray-200 rounded" + > + <div + *ngFor=" + let row of firmwareRowsSignal(); + let i = index; + trackBy: trackByFirmwareRowIndex + " + class="flex flex-col gap-1" + > + <div class="flex flex-row items-end gap-3 w-full"> + <div class="w-32"> + <label class="block text-xs text-blue-500 mb-1">Target:</label> + <mat-select + [value]="row.target" + (selectionChange)="onFirmwareTargetChanged(i, $event.value)" + [disabled]="isScheduleRunLoading().show" + class="border-b border-gray-400 pb-1 text-sm" + > + <mat-option + *ngFor="let targetOpt of firmwareTargetOptions" + [value]="targetOpt.value" + > + {{ targetOpt.label }} + </mat-option> + </mat-select> + </div> + + <div class="w-24"> + <label class="block text-xs text-blue-500 mb-1">Type:</label> + <mat-select + [value]="row.type" + (selectionChange)="onFirmwareTypeChanged(i, $event.value)" + [disabled]="isScheduleRunLoading().show" + class="border-b border-gray-400 pb-1 text-sm" + > + <mat-option + *ngFor="let typeOpt of firmwareTypeOptions" + [value]="typeOpt.value" + > + {{ typeOpt.label }} + </mat-option> + </mat-select> + </div> + + <div class="flex-1"> + <label class="block text-xs text-blue-500 mb-1">Version:</label> + <input + type="text" + placeholder="e.g. 1.2.3" + [value]="row.version" + (input)="onFirmwareVersionInputChanged(i, $event)" + [disabled]="isScheduleRunLoading().show" + class="w-full block border-b border-gray-400 pb-1 focus:border-blue-500 bg-transparent outline-none text-sm font-light font-sans disabled:text-gray-300" + [class.border-red-500]="firmwareRowErrors()[i] !== ''" + /> + </div> + + <button + type="button" + class="flex items-center justify-center w-8 h-8 rounded hover:bg-gray-100 text-gray-600 disabled:text-gray-300 disabled:hover:bg-transparent disabled:cursor-not-allowed transition-colors" + (click)="onRemoveFirmwareRowClicked(i)" + [disabled]=" + firmwareRowsSignal().length <= 1 || + isScheduleRunLoading().show + " + title="Remove firmware version" + > + <span class="material-icon">remove</span> + </button> + </div> + + <p + *ngIf="firmwareRowErrors()[i] !== ''" + class="text-red-500 text-xs m-0" + > + {{ firmwareRowErrors()[i] }} + </p> + </div> + + <div class="flex justify-start mt-1"> + <button + type="button" + class="flex items-center justify-center w-8 h-8 rounded hover:bg-gray-100 text-blue-500 disabled:text-gray-300 disabled:hover:bg-transparent disabled:cursor-not-allowed transition-colors" + (click)="onAddFirmwareRowClicked()" + [disabled]=" + firmwareRowsSignal().length >= MAX_FIRMWARE_ROWS || + isScheduleRunLoading().show + " + title="Add firmware version" + > + <span class="material-icon">add</span> + </button> + </div> + </div> + <app-basic-selector (select)="onPropsChanged('pool', $event)" [options]="poolOptions()"
diff --git a/src/satlab-ui/src/app/run_suite/android/auto-qual/auto-qual.component.spec.ts b/src/satlab-ui/src/app/run_suite/android/auto-qual/auto-qual.component.spec.ts index a990841..fbab810 100644 --- a/src/satlab-ui/src/app/run_suite/android/auto-qual/auto-qual.component.spec.ts +++ b/src/satlab-ui/src/app/run_suite/android/auto-qual/auto-qual.component.spec.ts
@@ -1,14 +1,56 @@ import {ComponentFixture, TestBed} from '@angular/core/testing'; +import {RouterTestingModule} from '@angular/router/testing'; +import {NoopAnimationsModule} from '@angular/platform-browser/animations'; +import {of} from 'rxjs'; +import {AndroidService} from 'app/services/android.service'; +import {SatlabRpcService} from 'app/services/satlab-rpc.service'; +import {NotificationService} from 'app/services/notification.service'; +import { + CreateTestEffortResponse, + ListTestEffortsResponse, +} from 'app/services/satlabrpc_pb'; import {AutoQualComponent} from './auto-qual.component'; describe('AutoQualComponent', () => { let component: AutoQualComponent; let fixture: ComponentFixture<AutoQualComponent>; + let androidServiceSpy: jasmine.SpyObj<AndroidService>; beforeEach(() => { + androidServiceSpy = jasmine.createSpyObj('AndroidService', [ + 'listTestEfforts', + 'createTestEffort', + 'cancelTestEffort', + 'validateBuild', + 'listBranches', + 'listTargets', + 'listBuilds', + ]); + androidServiceSpy.listTestEfforts.and.returnValue( + of(new ListTestEffortsResponse()) + ); + androidServiceSpy.createTestEffort.and.returnValue( + of(new CreateTestEffortResponse().setId('effort-1')) + ); + + const rpcServiceSpy = jasmine.createSpyObj('SatlabRpcService', [ + 'listEnrolledDUTs', + ]); + rpcServiceSpy.listEnrolledDUTs.and.returnValue(Promise.resolve([])); + + const notificationSpy = jasmine.createSpyObj('NotificationService', [ + 'info', + 'error', + ]); + TestBed.configureTestingModule({ - imports: [AutoQualComponent], + imports: [AutoQualComponent, RouterTestingModule, NoopAnimationsModule], + providers: [ + {provide: AndroidService, useValue: androidServiceSpy}, + {provide: SatlabRpcService, useValue: rpcServiceSpy}, + {provide: NotificationService, useValue: notificationSpy}, + ], }); fixture = TestBed.createComponent(AutoQualComponent); component = fixture.componentInstance; @@ -18,4 +60,110 @@ it('should create', () => { expect(component).toBeTruthy(); }); + + it('should enforce min 1 and max 8 firmware rows', () => { + const comp = component as unknown as { + firmwareRowsSignal: () => {target: string; type: string; version: string}[]; + onAddFirmwareRowClicked: () => void; + onRemoveFirmwareRowClicked: (index: number) => void; + }; + + expect(comp.firmwareRowsSignal().length).toBe(1); + + // Cannot delete the last remaining row + comp.onRemoveFirmwareRowClicked(0); + expect(comp.firmwareRowsSignal().length).toBe(1); + + // Add rows up to the max of 8 + for (let i = 0; i < 10; i++) { + comp.onAddFirmwareRowClicked(); + } + expect(comp.firmwareRowsSignal().length).toBe(8); + + // Can delete when > 1 row + comp.onRemoveFirmwareRowClicked(7); + expect(comp.firmwareRowsSignal().length).toBe(7); + }); + + it('should validate firmware version format and duplicate target/type pairs', () => { + const comp = component as unknown as { + isFirmwareVersionEnabledSignal: {set: (v: boolean) => void}; + firmwareRowsSignal: { + set: (v: {target: string; type: string; version: string}[]) => void; + }; + firmwareRowErrors: () => string[]; + isFirmwareValid: () => boolean; + }; + + comp.isFirmwareVersionEnabledSignal.set(true); + + // Blank version is valid + comp.firmwareRowsSignal.set([{target: 'ap', type: 'ro', version: ''}]); + expect(comp.isFirmwareValid()).toBeTrue(); + + // Invalid version format + comp.firmwareRowsSignal.set([{target: 'ap', type: 'ro', version: '1.2'}]); + expect(comp.isFirmwareValid()).toBeFalse(); + expect(comp.firmwareRowErrors()[0]).toContain('major.minor.patch'); + + // Valid version format + comp.firmwareRowsSignal.set([ + {target: 'ap', type: 'ro', version: '16759.22.0'}, + ]); + expect(comp.isFirmwareValid()).toBeTrue(); + + // Duplicate (target, type) combination + comp.firmwareRowsSignal.set([ + {target: 'ap', type: 'ro', version: '16759.22.0'}, + {target: 'ap', type: 'ro', version: '16759.23.0'}, + ]); + expect(comp.isFirmwareValid()).toBeFalse(); + expect(comp.firmwareRowErrors()[0]).toContain('Duplicate'); + }); + + it('should forward non-empty firmware rows to createTestEffort', () => { + const comp = component as unknown as { + boardSignal: {set: (v: string) => void}; + modelSignal: {set: (v: string) => void}; + branchSignal: {set: (v: string) => void}; + boardTargetSignal: {set: (v: string) => void}; + testTargetSignal: {set: (v: string) => void}; + poolSignal: {set: (v: string) => void}; + buildSignal: {set: (v: string) => void}; + validBuildSignal: {set: (v: string) => void}; + testplanSignal: {set: (v: string) => void}; + isFirmwareVersionEnabledSignal: {set: (v: boolean) => void}; + firmwareRowsSignal: { + set: (v: {target: string; type: string; version: string}[]) => void; + }; + onScheduleClicked: () => void; + }; + + comp.boardSignal.set('tanjiro'); + comp.modelSignal.set('sapphire'); + comp.branchSignal.set('arsp-26Q3-desktop-release'); + comp.boardTargetSignal.set('sapphire-userdebug'); + comp.testTargetSignal.set('test_suites_x86_64'); + comp.poolSignal.set('satlab-pool'); + comp.buildSignal.set('12345'); + comp.validBuildSignal.set('12345'); + comp.testplanSignal.set('dts/avs/firmware'); + comp.isFirmwareVersionEnabledSignal.set(true); + comp.firmwareRowsSignal.set([ + {target: 'ap', type: 'ro', version: '16759.22.0'}, + {target: 'ap', type: 'rw', version: '16759.22.0'}, + {target: 'ec', type: 'ro', version: ''}, + ]); + + comp.onScheduleClicked(); + + expect(androidServiceSpy.createTestEffort).toHaveBeenCalledWith( + jasmine.objectContaining({ + firmwareConfigs: [ + {target: 'ap', type: 'ro', major: 16759, minor: 22, patch: 0}, + {target: 'ap', type: 'rw', major: 16759, minor: 22, patch: 0}, + ], + }) + ); + }); });
diff --git a/src/satlab-ui/src/app/run_suite/android/auto-qual/auto-qual.component.ts b/src/satlab-ui/src/app/run_suite/android/auto-qual/auto-qual.component.ts index 09cba3a..b0e1e0e 100644 --- a/src/satlab-ui/src/app/run_suite/android/auto-qual/auto-qual.component.ts +++ b/src/satlab-ui/src/app/run_suite/android/auto-qual/auto-qual.component.ts
@@ -19,7 +19,12 @@ import {AutocompleteComponent} from 'app/run_suite/common/autocomplete/autocomplete.component'; import {BasicSelectorComponent} from 'app/run_suite/common/basic-selector/basic-selector.component'; import {LoadingComponent} from 'app/run_suite/common/loading/loading.component'; -import {AndroidService} from 'app/services/android.service'; +import { + AndroidService, + FirmwareConfigParam, + FirmwareReadabilityType, + FirmwareTargetType, +} from 'app/services/android.service'; import {NotificationService} from 'app/services/notification.service'; import {SatlabRpcService} from 'app/services/satlab-rpc.service'; import {TestEffort} from 'app/services/satlabrpc_pb'; @@ -27,11 +32,18 @@ import {resetSignals, wrapperLoading} from 'app/utils/operators'; import {from} from 'rxjs'; import {ActivatedRoute, Router, RouterModule} from '@angular/router'; +import {MatSelectModule} from '@angular/material/select'; import {MatSlideToggleModule} from '@angular/material/slide-toggle'; import {ALBuildBasic, ICustomSettings} from 'app/models/run_suite_fields'; import {AdvancedSettingsComponent} from 'app/run_suite/common/advanced-settings/advanced-settings.component'; import {AndroidBuildPickerComponent} from '../../android/common/android-build-picker/android-build-picker.component'; +export interface FirmwareRow { + target: FirmwareTargetType; + type: FirmwareReadabilityType; + version: string; +} + export enum Status { SCHEDULED = 'Scheduled', IN_PROGRESS = 'In progress', @@ -51,6 +63,7 @@ BasicSelectorComponent, AutocompleteComponent, RouterModule, + MatSelectModule, MatSlideToggleModule, AndroidBuildPickerComponent, AdvancedSettingsComponent, @@ -197,6 +210,66 @@ protected testTargetSignal = signal<string>(''); protected testValidBuildSignal = signal<string>(''); + protected readonly firmwareTargetOptions: { + value: FirmwareTargetType; + label: string; + }[] = [ + {value: 'ap', label: 'AP'}, + {value: 'ec', label: 'EC'}, + {value: 'gsc', label: 'GSC'}, + {value: 'fpmcu', label: 'FPMCU'}, + ]; + protected readonly firmwareTypeOptions: { + value: FirmwareReadabilityType; + label: string; + }[] = [ + {value: 'ro', label: 'RO'}, + {value: 'rw', label: 'RW'}, + ]; + protected readonly MAX_FIRMWARE_ROWS = + this.firmwareTargetOptions.length * this.firmwareTypeOptions.length; + private readonly FIRMWARE_VERSION_REGEX = /^\d+\.\d+\.\d+$/; + private readonly MAX_INT32 = 2147483647; + + protected isFirmwareVersionEnabledSignal = signal<boolean>(false); + protected firmwareRowsSignal = signal<FirmwareRow[]>([ + {target: 'ap', type: 'ro', version: ''}, + ]); + + protected firmwareRowErrors = computed<string[]>(() => { + const rows = this.firmwareRowsSignal(); + const pairCounts = new Map<string, number>(); + for (const row of rows) { + const key = `${row.target}:${row.type}`; + pairCounts.set(key, (pairCounts.get(key) || 0) + 1); + } + + return rows.map(row => { + const key = `${row.target}:${row.type}`; + if ((pairCounts.get(key) || 0) > 1) { + return 'Duplicate target and type combination.'; + } + const trimmed = row.version.trim(); + if (trimmed !== '') { + if (!this.FIRMWARE_VERSION_REGEX.test(trimmed)) { + return 'Format must be major.minor.patch (e.g. 1.2.3).'; + } + const parts = trimmed.split('.').map(Number); + if (parts.some(p => p > this.MAX_INT32)) { + return 'Version numbers are out of range.'; + } + } + return ''; + }); + }); + + protected isFirmwareValid = computed<boolean>(() => { + if (!this.isFirmwareVersionEnabledSignal()) { + return true; + } + return this.firmwareRowErrors().every(err => err === ''); + }); + protected notAvailableMsg = signal<string>(''); protected isRunnable = computed(() => { @@ -229,6 +302,7 @@ validBuild !== '' && testplan !== '' && crossBranchValid && + this.isFirmwareValid() && !errMsg ); }); @@ -463,6 +537,78 @@ this.isCrossBranchSignal.set(value); } + protected onFirmwareVersionEnabledChanged(value: boolean) { + this.isFirmwareVersionEnabledSignal.set(value); + if (!value) { + this.firmwareRowsSignal.set([{target: 'ap', type: 'ro', version: ''}]); + } + } + + protected onAddFirmwareRowClicked() { + const rows = this.firmwareRowsSignal(); + if (rows.length >= this.MAX_FIRMWARE_ROWS) { + return; + } + + const usedPairs = new Set(rows.map(r => `${r.target}:${r.type}`)); + let nextTarget: FirmwareTargetType = 'ap'; + let nextType: FirmwareReadabilityType = 'ro'; + let found = false; + + for (const targetOpt of this.firmwareTargetOptions) { + for (const typeOpt of this.firmwareTypeOptions) { + if (!usedPairs.has(`${targetOpt.value}:${typeOpt.value}`)) { + nextTarget = targetOpt.value; + nextType = typeOpt.value; + found = true; + break; + } + } + if (found) { + break; + } + } + + this.firmwareRowsSignal.set([ + ...rows, + {target: nextTarget, type: nextType, version: ''}, + ]); + } + + protected onRemoveFirmwareRowClicked(index: number) { + const rows = this.firmwareRowsSignal(); + if (rows.length <= 1) { + return; + } + this.firmwareRowsSignal.set(rows.filter((_, i) => i !== index)); + } + + protected onFirmwareTargetChanged(index: number, target: FirmwareTargetType) { + this.firmwareRowsSignal.update(rows => + rows.map((row, i) => (i === index ? {...row, target} : row)) + ); + } + + protected onFirmwareTypeChanged( + index: number, + type: FirmwareReadabilityType + ) { + this.firmwareRowsSignal.update(rows => + rows.map((row, i) => (i === index ? {...row, type} : row)) + ); + } + + protected onFirmwareVersionInputChanged(index: number, event: Event) { + const version = (event.target as HTMLInputElement).value; + this.firmwareRowsSignal.update(rows => + rows.map((row, i) => (i === index ? {...row, version} : row)) + ); + } + + protected trackByFirmwareRowIndex(index: number) { + return index; + } + protected onAdvancedSettingsChanged(settings: ICustomSettings) { this.customSettings = settings; this.skipBootPrerequisiteSignal.set(!!settings.skipBootPrerequisite); @@ -481,6 +627,24 @@ protected onScheduleClicked() { const testplan = this.testplanSignal(); const isCrossBranch = this.isCrossBranchSignal(); + const firmwareConfigs: FirmwareConfigParam[] = + this.isFirmwareVersionEnabledSignal() + ? this.firmwareRowsSignal() + .filter(row => row.version.trim() !== '') + .map(row => { + const [major, minor, patch] = row.version + .trim() + .split('.') + .map(Number); + return { + target: row.target, + type: row.type, + major, + minor, + patch, + }; + }) + : []; this.__createTestEffort({ board: this.boardSignal(), @@ -496,6 +660,7 @@ ? this.testValidBuildSignal() : this.validBuildSignal(), skipBootPrerequisite: this.skipBootPrerequisiteSignal(), + firmwareConfigs: firmwareConfigs.length > 0 ? firmwareConfigs : undefined, }); } @@ -585,6 +750,7 @@ testTarget?: string; testBuild?: string; skipBootPrerequisite?: boolean; + firmwareConfigs?: FirmwareConfigParam[]; }) { wrapperLoading( from( @@ -602,6 +768,7 @@ testTarget: p.testTarget, testBuild: p.testBuild, skipBootPrerequisite: p.skipBootPrerequisite, + firmwareConfigs: p.firmwareConfigs, }) ), this.isScheduleRunLoading,
diff --git a/src/satlab-ui/src/app/services/android.service.spec.ts b/src/satlab-ui/src/app/services/android.service.spec.ts index 9473a2d..c99d1d9 100644 --- a/src/satlab-ui/src/app/services/android.service.spec.ts +++ b/src/satlab-ui/src/app/services/android.service.spec.ts
@@ -1,5 +1,7 @@ import {AndroidService} from './android.service'; +import {CreateTestEffortRequest, CreateTestEffortResponse, TestEffort} from './satlabrpc_pb'; import {TestBed} from '@angular/core/testing'; +import {firstValueFrom} from 'rxjs'; describe('AndroidService', () => { let service: AndroidService; @@ -12,4 +14,66 @@ it('should be created', () => { expect(service).toBeTruthy(); }); + + it('should populate firmwareConfigs in CreateTestEffortRequest', async () => { + let capturedRequest: CreateTestEffortRequest | undefined; + spyOn( + (service as unknown as {client: {createTestEffort: jasmine.Spy}}).client, + 'createTestEffort' + ).and.callFake((req: CreateTestEffortRequest) => { + capturedRequest = req; + return Promise.resolve(new CreateTestEffortResponse().setId('effort-1')); + }); + + await firstValueFrom( + service.createTestEffort({ + board: 'fatcat', + model: 'ruby', + branch: 'arsp-main', + target: 'ruby-userdebug', + build: '123456', + testplan: 'dts/avs/firmware', + pools: [{label: 'pool-1', type: 1}], + firmwareConfigs: [ + {target: 'ap', type: 'ro', major: 16759, minor: 22, patch: 0}, + {target: 'ec', type: 'rw', major: 16765, minor: 2, patch: 21}, + {target: 'gsc', type: 'ro', major: 1, minor: 2, patch: 3}, + {target: 'fpmcu', type: 'rw', major: 4, minor: 5, patch: 6}, + ], + }) + ); + + expect(capturedRequest).toBeDefined(); + const effort = capturedRequest!.getEffort()!; + const fwConfigs = effort.getFirmwareConfigsList(); + expect(fwConfigs.length).toBe(4); + + expect(fwConfigs[0].getFirmwareTarget()?.getAp()?.getMajorVersion()).toBe(16759); + expect(fwConfigs[0].getFirmwareTarget()?.getAp()?.getMinorVersion()).toBe(22); + expect(fwConfigs[0].getFirmwareTarget()?.getAp()?.getPatchNumber()).toBe(0); + expect(fwConfigs[0].getFirmwareTypesList()).toEqual([ + TestEffort.FirmwareType.FIRMWARE_TYPE_READ_ONLY, + ]); + + expect(fwConfigs[1].getFirmwareTarget()?.getEc()?.getMajorVersion()).toBe(16765); + expect(fwConfigs[1].getFirmwareTarget()?.getEc()?.getMinorVersion()).toBe(2); + expect(fwConfigs[1].getFirmwareTarget()?.getEc()?.getPatchNumber()).toBe(21); + expect(fwConfigs[1].getFirmwareTypesList()).toEqual([ + TestEffort.FirmwareType.FIRMWARE_TYPE_READ_WRITE, + ]); + + expect(fwConfigs[2].getFirmwareTarget()?.getGsc()?.getMajorVersion()).toBe(1); + expect(fwConfigs[2].getFirmwareTarget()?.getGsc()?.getMinorVersion()).toBe(2); + expect(fwConfigs[2].getFirmwareTarget()?.getGsc()?.getGeneration()).toBe(3); + expect(fwConfigs[2].getFirmwareTypesList()).toEqual([ + TestEffort.FirmwareType.FIRMWARE_TYPE_READ_ONLY, + ]); + + expect(fwConfigs[3].getFirmwareTarget()?.getFpmcu()?.getMajorVersion()).toBe(4); + expect(fwConfigs[3].getFirmwareTarget()?.getFpmcu()?.getMinorVersion()).toBe(5); + expect(fwConfigs[3].getFirmwareTarget()?.getFpmcu()?.getPatchNumber()).toBe(6); + expect(fwConfigs[3].getFirmwareTypesList()).toEqual([ + TestEffort.FirmwareType.FIRMWARE_TYPE_READ_WRITE, + ]); + }); });
diff --git a/src/satlab-ui/src/app/services/android.service.ts b/src/satlab-ui/src/app/services/android.service.ts index c7da164..23d4089 100644 --- a/src/satlab-ui/src/app/services/android.service.ts +++ b/src/satlab-ui/src/app/services/android.service.ts
@@ -31,6 +31,17 @@ export type XtsType = (typeof XTS_TYPES)[number]; +export type FirmwareTargetType = 'ap' | 'ec' | 'gsc' | 'fpmcu'; +export type FirmwareReadabilityType = 'ro' | 'rw'; + +export interface FirmwareConfigParam { + target: FirmwareTargetType; + type: FirmwareReadabilityType; + major: number; + minor: number; + patch: number; +} + export interface XtsAndroidBuildPin { testBuild: string; testBranch: string; @@ -353,6 +364,7 @@ testTarget?: string; testBuild?: string; skipBootPrerequisite?: boolean; + firmwareConfigs?: FirmwareConfigParam[]; }) { const poolLists = params.pools.map(p => { return new TestEffort.Pool().setLabel(p.label).setType(p.type); @@ -381,6 +393,46 @@ testEffort.setAndroidTestTarget(androidTestTarget); } + if (params.firmwareConfigs && params.firmwareConfigs.length > 0) { + const fwConfigs = params.firmwareConfigs.map(cfg => { + const fwTarget = new TestEffort.FirmwareTarget(); + if (cfg.target === 'gsc') { + const gscVer = new TestEffort.GscTestEffortVersion() + .setMajorVersion(cfg.major) + .setMinorVersion(cfg.minor) + .setGeneration(cfg.patch); + fwTarget.setGsc(gscVer); + } else { + const stdVer = new TestEffort.FirmwareTestEffortVersion() + .setMajorVersion(cfg.major) + .setMinorVersion(cfg.minor) + .setPatchNumber(cfg.patch); + switch (cfg.target) { + case 'ap': + fwTarget.setAp(stdVer); + break; + case 'ec': + fwTarget.setEc(stdVer); + break; + case 'fpmcu': + fwTarget.setFpmcu(stdVer); + break; + } + } + + const fwType = + cfg.type === 'ro' + ? TestEffort.FirmwareType.FIRMWARE_TYPE_READ_ONLY + : TestEffort.FirmwareType.FIRMWARE_TYPE_READ_WRITE; + + return new TestEffort.FirmwareConfig() + .setFirmwareTarget(fwTarget) + .setFirmwareTypesList([fwType]); + }); + + testEffort.setFirmwareConfigsList(fwConfigs); + } + const req = new CreateTestEffortRequest().setEffort(testEffort); return from(this.client.createTestEffort(req, {})); }