blob: be218494937ec555134c30e805aee79624287b1d [file] [log] [blame] [edit]
// Copyright 2022 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package main
import (
"context"
"fmt"
"io"
"log"
"net/url"
"os"
"os/exec"
"path"
"strings"
"testing"
longrunning "go.chromium.org/chromiumos/config/go/longrunning"
"go.chromium.org/chromiumos/test/provision/v2/cros-fw-provision/cli"
firmwareservice "go.chromium.org/chromiumos/test/provision/v2/cros-fw-provision/service"
state_machine "go.chromium.org/chromiumos/test/provision/v2/cros-fw-provision/state-machine"
"go.chromium.org/chromiumos/test/provision/v2/mock_api"
"github.com/golang/mock/gomock"
conf "go.chromium.org/chromiumos/config/go"
build_api "go.chromium.org/chromiumos/config/go/build/api"
"go.chromium.org/chromiumos/config/go/test/api"
"google.golang.org/grpc"
"google.golang.org/protobuf/encoding/prototext"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/types/known/anypb"
)
type curlMatcher struct {
target string
fileMatcher gomock.Matcher
log *log.Logger
}
func newCurlMatcher(target string, fileMatcher gomock.Matcher, log *log.Logger) curlMatcher {
return curlMatcher{
target: target,
fileMatcher: fileMatcher,
log: log,
}
}
func (c curlMatcher) Matches(x interface{}) bool {
req, ok := x.(*api.ExecCommandRequest)
if ok {
if req.Command != "curl" {
return false
}
if len(req.Args) != 5 {
return false
}
if req.Args[0] != "-f" || req.Args[1] != "-S" || req.Args[2] != "-o" || req.Args[3] != c.target {
return false
}
idx := strings.Index(req.Args[4], "?file=")
if idx < 0 {
return false
}
filename, err := url.QueryUnescape(req.Args[4][idx+6:])
if err != nil {
panic(err)
}
// Remove trailing quote
filename = filename[:len(filename)-1]
return c.fileMatcher.Matches(filename)
}
return false
}
func (c curlMatcher) String() string {
return fmt.Sprintf("curl -f -S -o %s url?file=(%s)", c.target, c.fileMatcher)
}
type yamlReader struct {
*strings.Reader
}
func (yr yamlReader) Close() error {
return nil
}
type rpcMsg struct {
msg proto.Message
}
func (r *rpcMsg) Matches(msg interface{}) bool {
m, ok := msg.(proto.Message)
if !ok {
return false
}
return proto.Equal(m, r.msg)
}
func (r *rpcMsg) String() string {
return fmt.Sprintf("is %s", prototext.Format(r.msg))
}
func (r *rpcMsg) Got(got interface{}) string {
return prototext.Format(got.(proto.Message))
}
type dutServiceExecCommandClient struct {
Response *api.ExecCommandResponse
grpc.ClientStream
}
func (x *dutServiceExecCommandClient) Recv() (*api.ExecCommandResponse, error) {
if x.Response != nil {
tmp := x.Response
x.Response = nil
return tmp, nil
}
return nil, io.EOF
}
func newResponse(pb *api.ExecCommandResponse) *dutServiceExecCommandClient {
return &dutServiceExecCommandClient{
Response: pb,
}
}
type dutServiceFetchFileClient struct {
Response *api.File
grpc.ClientStream
}
func (x *dutServiceFetchFileClient) Recv() (*api.File, error) {
if x.Response != nil {
tmp := x.Response
x.Response = nil
return tmp, nil
}
return nil, io.EOF
}
func newFileResponse(pb *api.File) *dutServiceFetchFileClient {
return &dutServiceFetchFileClient{
Response: pb,
}
}
func TestDetailedRequestSSHStates(t *testing.T) {
fakeGSPath := "gs://chromeos-image-archive/board-firmware-branch/R123-12345.0.0/board/firmware_from_source.tar.bz2"
makeRequest := func(main_rw, main_ro, ec_ro bool) *api.InstallRequest {
fakePayload := &build_api.FirmwarePayload{FirmwareImage: &build_api.FirmwarePayload_FirmwareImagePath{FirmwareImagePath: &conf.StoragePath{HostType: conf.StoragePath_GS, Path: fakeGSPath}}}
FirmwareConfig := build_api.FirmwareConfig{}
if main_rw {
FirmwareConfig.MainRwPayload = fakePayload
}
if main_ro {
FirmwareConfig.MainRoPayload = fakePayload
}
if ec_ro {
FirmwareConfig.EcRoPayload = fakePayload
}
any, err := anypb.New(&api.FirmwareProvisionInstallMetadata{
FirmwareConfig: &FirmwareConfig,
})
if err != nil {
t.Fatal("any.new failed: ", err)
}
req := &api.InstallRequest{
Metadata: any,
}
return req
}
checkStateName := func(st state_machine.ServiceState, expectedStateName string) {
if st == nil {
if len(expectedStateName) > 0 {
t.Fatalf("expected state %v. got: nil state", expectedStateName)
}
return
}
stateName := st.Name()
if stateName != expectedStateName {
t.Fatalf("expected state %v. got: %v", expectedStateName, stateName)
}
}
type TestCase struct {
// inputs
main_rw, main_ro, ec_ro bool
configYAML string
// These are the first files that will exist in the archive.
apImageWithinArchive string
ecImageWithinArchive string
npcxImageWithinArchive string
// expected outputs
updateRw, updateRo bool
expectConstructorError bool
expectedResponse *api.FirmwareProvisionResponse
}
fullVersion := &api.FirmwareProvisionResponse{
ApRoVersion: "roversion",
ApRwVersion: "rwversion",
EcRoVersion: "ecroversion",
EcRwVersion: "ecrwversion",
}
roOnlyVersion := &api.FirmwareProvisionResponse{
ApRoVersion: "roversion",
ApRwVersion: "roversioninrw",
EcRoVersion: "ecroversion",
EcRwVersion: "ecroversioninrw",
}
testCases := []TestCase{
{ /*in*/ false, false, false, "", "image.bin", "ec.bin", "npcx_monitor.bin" /*out*/, false, false /*err*/, true, fullVersion},
{ /*in*/ true, false, false, "", "image.bin", "ec.bin", "npcx_monitor.bin" /*out*/, true, false /*err*/, false, fullVersion},
{ /*in*/ false, true, false, "", "image.bin", "ec.bin", "npcx_monitor.bin" /*out*/, false, true /*err*/, false, roOnlyVersion},
{ /*in*/ false, false, true, "", "image.bin", "ec.bin", "npcx_monitor.bin" /*out*/, false, true /*err*/, false, roOnlyVersion},
{ /*in*/ false, true, true, "", "image.bin", "ec.bin", "npcx_monitor.bin" /*out*/, false, true /*err*/, false, roOnlyVersion},
{ /*in*/ true, true, true, "", "image.bin", "ec.bin", "npcx_monitor.bin" /*out*/, true, true /*err*/, false, fullVersion},
{ /*in*/ true, true, false, "", "image.bin", "ec.bin", "npcx_monitor.bin" /*out*/, true, true /*err*/, false, fullVersion},
{ /*in*/ true, true, true, `{
"chromeos": {
"configs": [{
"firmware": {
"build-targets": {
"coreboot": "fromcoreboot",
"ec": "fromec",
"zephyr-ec": "fromzephyr"
},
"image-name": "fromimagename"
},
"name": "test_model"
}]
}
}`, "image-fromcoreboot.bin", "fromzephyr/ec.bin", "fromzephyr/npcx_monitor.bin" /*out*/, true, true /*err*/, false, fullVersion},
}
log, _ := cli.SetUpLog(cli.DefaultLogDirectory)
for _, testCase := range testCases {
// Set up the mock.
ctrl := gomock.NewController(t)
dsc := mock_api.NewMockDutServiceClient(ctrl)
// Create FirmwareService.
ctx := context.Background()
req := makeRequest(testCase.main_rw, testCase.main_ro, testCase.ec_ro)
log.Printf(" Test Case: %#v\n Request: {%s}", testCase, req.String())
cacheServer := url.URL{
Scheme: "http",
Host: "1.2.3.4:5678",
}
fws, err := firmwareservice.NewFirmwareService(
ctx,
dsc,
nil,
cacheServer,
"test_board",
"test_model",
false,
req,
)
// Check if init error is expected/got.
if err != nil {
if testCase.expectConstructorError {
continue
}
t.Fatalf("failed to create FirmwareService with test case %#v: %v", testCase, err)
}
if err == nil && testCase.expectConstructorError {
t.Fatalf("expected constructor error for test case %#v. got: %v", testCase, err)
}
// Check expected states.
if testCase.updateRo != fws.UpdateRo() {
t.Fatalf("test case %#v expects updateRo to be %v. got: %v.", testCase, testCase.updateRo, fws.UpdateRo())
}
if testCase.updateRw != fws.UpdateRw() {
t.Fatalf("test case %#v expects updateRw to be %v. got: %v.", testCase, testCase.updateRw, fws.UpdateRw())
}
// Start with the first state of the service.
st := state_machine.NewFirmwarePrepareState(fws)
// Confirm state name is Prepare.
checkStateName(st, state_machine.PrepareStateName)
// Set mock expectations.
if testCase.configYAML == "" {
gomock.InOrder(
dsc.EXPECT().ExecCommand(gomock.Any(), &rpcMsg{msg: &api.ExecCommandRequest{
Command: "crosid",
}}).MinTimes(1).Return(newResponse(&api.ExecCommandResponse{ExitInfo: &api.ExecCommandResponse_ExitInfo{}}), nil),
dsc.EXPECT().ExecCommand(gomock.Any(), &rpcMsg{msg: &api.ExecCommandRequest{
Args: []string{"[", "-e", "/usr/share/chromeos-config/yaml/config.yaml", "]", "&&", "echo", "-n", "1", "||", "echo", "-n", "0"},
}}).MinTimes(1).Return(newResponse(&api.ExecCommandResponse{ExitInfo: &api.ExecCommandResponse_ExitInfo{}, Stdout: []byte("0")}), nil),
)
} else {
td := t.TempDir()
t.Logf("Writing %s", path.Join(td, "config.yaml"))
if err := os.WriteFile(path.Join(td, "config.yaml"), []byte(testCase.configYAML), 0666); err != nil {
t.Fatal("Failed to write config.yaml: ", err)
}
if out, err := exec.Command("tar", "-c", "--mode=a+rw", "--gzip", "-C", td, "-f", path.Join(td, "config.yaml.tar.gz"), "config.yaml").CombinedOutput(); err != nil {
t.Fatalf("Failed to tar -config.yaml: %v\n%s", err, string(out))
}
yamlTar, err := os.ReadFile(path.Join(td, "config.yaml.tar.gz"))
if err != nil {
t.Fatal("Failed to read config.yaml.tar.gz: ", err)
}
gomock.InOrder(
dsc.EXPECT().ExecCommand(gomock.Any(), &rpcMsg{msg: &api.ExecCommandRequest{
Command: "crosid",
}}).MinTimes(1).Return(newResponse(&api.ExecCommandResponse{ExitInfo: &api.ExecCommandResponse_ExitInfo{}}), nil),
dsc.EXPECT().ExecCommand(gomock.Any(), &rpcMsg{msg: &api.ExecCommandRequest{
Args: []string{"[", "-e", "/usr/share/chromeos-config/yaml/config.yaml", "]", "&&", "echo", "-n", "1", "||", "echo", "-n", "0"},
}}).Return(newResponse(&api.ExecCommandResponse{ExitInfo: &api.ExecCommandResponse_ExitInfo{}, Stdout: []byte("1")}), nil),
dsc.EXPECT().FetchFile(gomock.Any(), &api.FetchFileRequest{
File: "/usr/share/chromeos-config/yaml/config.yaml",
}).Return(newFileResponse(&api.File{File: yamlTar}), nil),
)
}
stagingURL := "'http://1.2.3.4:5678/stage/?archive_url=gs%3A%2Fchromeos-image-archive%2Fboard-firmware-branch%2FR123-12345.0.0%2Fboard&files=firmware_from_source.tar.bz2'"
mkdirCall := dsc.EXPECT().ExecCommand(gomock.Any(), &rpcMsg{msg: &api.ExecCommandRequest{
Command: "mktemp",
Args: []string{"-d", "--tmpdir=/var/tmp", "cros-fw-provision.XXXXXXXXX.board"},
}}).Return(newResponse(&api.ExecCommandResponse{ExitInfo: &api.ExecCommandResponse_ExitInfo{}, Stdout: []byte("/var/tmp/someTempDir\n")}), nil)
if testCase.main_ro {
stagingCall := dsc.EXPECT().ExecCommand(gomock.Any(), &rpcMsg{msg: &api.ExecCommandRequest{
Command: "curl",
Args: []string{"-f", "-S", stagingURL},
}}).MinTimes(1).Return(newResponse(&api.ExecCommandResponse{ExitInfo: &api.ExecCommandResponse_ExitInfo{}}), nil)
dsc.EXPECT().ExecCommand(gomock.Any(), newCurlMatcher("/var/tmp/someTempDir/bios.bin", gomock.Eq("image-test_model.bin"), log)).
After(stagingCall).After(mkdirCall).MaxTimes(1).
Return(newResponse(&api.ExecCommandResponse{ExitInfo: &api.ExecCommandResponse_ExitInfo{Status: 1}, Stderr: []byte("The requested URL returned error: 404\n")}), nil)
dsc.EXPECT().ExecCommand(gomock.Any(), newCurlMatcher("/var/tmp/someTempDir/bios.bin", gomock.Eq("image-test_board.bin"), log)).
After(stagingCall).After(mkdirCall).MaxTimes(1).
Return(newResponse(&api.ExecCommandResponse{ExitInfo: &api.ExecCommandResponse_ExitInfo{Status: 1}, Stderr: []byte("The requested URL returned error: 404\n")}), nil)
dsc.EXPECT().ExecCommand(gomock.Any(), newCurlMatcher("/var/tmp/someTempDir/bios.bin", gomock.Eq(testCase.apImageWithinArchive), log)).
After(stagingCall).After(mkdirCall).Times(1).
Return(newResponse(&api.ExecCommandResponse{ExitInfo: &api.ExecCommandResponse_ExitInfo{}}), nil)
}
if testCase.ec_ro {
stagingCall := dsc.EXPECT().ExecCommand(gomock.Any(), &rpcMsg{msg: &api.ExecCommandRequest{
Command: "curl",
Args: []string{"-f", "-S", stagingURL},
}}).MinTimes(1).Return(newResponse(&api.ExecCommandResponse{ExitInfo: &api.ExecCommandResponse_ExitInfo{}}), nil)
dsc.EXPECT().ExecCommand(gomock.Any(), newCurlMatcher("/var/tmp/someTempDir/ec.bin", gomock.Eq("test_board/ec.bin"), log)).
After(stagingCall).After(mkdirCall).MaxTimes(1).
Return(newResponse(&api.ExecCommandResponse{ExitInfo: &api.ExecCommandResponse_ExitInfo{Status: 1}, Stderr: []byte("The requested URL returned error: 404\n")}), nil)
dsc.EXPECT().ExecCommand(gomock.Any(), newCurlMatcher("/var/tmp/someTempDir/ec.bin", gomock.Eq("test_model/ec.bin"), log)).
After(stagingCall).After(mkdirCall).MaxTimes(1).
Return(newResponse(&api.ExecCommandResponse{ExitInfo: &api.ExecCommandResponse_ExitInfo{Status: 1}, Stderr: []byte("The requested URL returned error: 404\n")}), nil)
dsc.EXPECT().ExecCommand(gomock.Any(), newCurlMatcher("/var/tmp/someTempDir/ec.bin", gomock.Eq(testCase.ecImageWithinArchive), log)).
After(stagingCall).After(mkdirCall).Times(1).
Return(newResponse(&api.ExecCommandResponse{ExitInfo: &api.ExecCommandResponse_ExitInfo{}}), nil)
dsc.EXPECT().ExecCommand(gomock.Any(), newCurlMatcher("/var/tmp/someTempDir/npcx_monitor.bin", gomock.Eq(testCase.npcxImageWithinArchive), log)).
After(stagingCall).After(mkdirCall).Times(1).
Return(newResponse(&api.ExecCommandResponse{ExitInfo: &api.ExecCommandResponse_ExitInfo{}}), nil)
}
if testCase.main_rw {
stagingCall := dsc.EXPECT().ExecCommand(gomock.Any(), &rpcMsg{msg: &api.ExecCommandRequest{
Command: "curl",
Args: []string{"-f", "-S", stagingURL},
}}).MinTimes(1).Return(newResponse(&api.ExecCommandResponse{ExitInfo: &api.ExecCommandResponse_ExitInfo{}}), nil)
dsc.EXPECT().ExecCommand(gomock.Any(), newCurlMatcher("/var/tmp/someTempDir/bios.bin", gomock.Eq("image-test_model.bin"), log)).
After(stagingCall).After(mkdirCall).MaxTimes(1).
Return(newResponse(&api.ExecCommandResponse{ExitInfo: &api.ExecCommandResponse_ExitInfo{Status: 1}, Stderr: []byte("The requested URL returned error: 404\n")}), nil)
dsc.EXPECT().ExecCommand(gomock.Any(), newCurlMatcher("/var/tmp/someTempDir/bios.bin", gomock.Eq("image-test_board.bin"), log)).
After(stagingCall).After(mkdirCall).MaxTimes(1).
Return(newResponse(&api.ExecCommandResponse{ExitInfo: &api.ExecCommandResponse_ExitInfo{Status: 1}, Stderr: []byte("The requested URL returned error: 404\n")}), nil)
dsc.EXPECT().ExecCommand(gomock.Any(), newCurlMatcher("/var/tmp/someTempDir/bios.bin", gomock.Eq(testCase.apImageWithinArchive), log)).
After(stagingCall).After(mkdirCall).Times(1).
Return(newResponse(&api.ExecCommandResponse{ExitInfo: &api.ExecCommandResponse_ExitInfo{}}), nil)
}
// Execute the state and proceed.
_, _, err = st.Execute(ctx, log)
if err != nil {
t.Fatal(err)
}
st = st.Next()
if testCase.updateRo {
// Confirm state name is RO.
checkStateName(st, state_machine.UpdateRoStateName)
// Set mock expectations.
expectedFutilityArgs := []string{"update", "--mode=recovery"}
expectedFutilityPyArgs := "'update', '--mode=recovery'"
expectedFutilityImageArgs := []string{}
if testCase.main_ro {
expectedFutilityImageArgs = append(expectedFutilityImageArgs, "--image=/var/tmp/someTempDir/bios.bin")
expectedFutilityPyArgs += ", '--image=/var/tmp/someTempDir/bios.bin'"
}
if testCase.ec_ro {
expectedFutilityImageArgs = append(expectedFutilityImageArgs, "--ec_image=/var/tmp/someTempDir/ec.bin")
expectedFutilityPyArgs += ", '--ec_image=/var/tmp/someTempDir/ec.bin'"
}
expectedFutilityArgs = append(expectedFutilityArgs, expectedFutilityImageArgs...)
expectedFutilityArgs = append(expectedFutilityArgs, "--wp=0")
expectedFutilityPyArgs += ", '--wp=0'"
expectedFutilityManifestArgs := append([]string{"update", "--manifest"}, expectedFutilityImageArgs...)
gomock.InOrder(
dsc.EXPECT().ExecCommand(gomock.Any(), &rpcMsg{msg: &api.ExecCommandRequest{
Command: "futility",
Args: expectedFutilityManifestArgs,
}}).Times(1).Return(newResponse(&api.ExecCommandResponse{ExitInfo: &api.ExecCommandResponse_ExitInfo{}, Stdout: []byte(`{
"default": {
"host": {
"versions": { "ro": "roversion", "rw": "roversioninrw" }
},
"ec": {
"versions": { "ro": "ecroversion", "rw": "ecroversioninrw" }
}
}
}`)}), nil),
)
if testCase.main_ro {
gomock.InOrder(
dsc.EXPECT().ExecCommand(gomock.Any(), &rpcMsg{msg: &api.ExecCommandRequest{
Command: "cbfstool",
Args: []string{"/var/tmp/someTempDir/bios.bin", "extract", "-r", "FW_MAIN_A", "-n", "ecrw.hash", "-f", "/var/tmp/someTempDir/bios.bin-ecrw.hash"},
}}).Return(newResponse(&api.ExecCommandResponse{ExitInfo: &api.ExecCommandResponse_ExitInfo{}}), nil),
dsc.EXPECT().ExecCommand(gomock.Any(), &rpcMsg{msg: &api.ExecCommandRequest{
Command: "od",
Args: []string{"-A", "n", "-t", "x1", "/var/tmp/someTempDir/bios.bin-ecrw.hash"},
}}).Return(newResponse(&api.ExecCommandResponse{
ExitInfo: &api.ExecCommandResponse_ExitInfo{}, Stdout: []byte(" 38 73 52 6f 4f f3 af 90 62 68 7c 30 91 13 09 ee\n 12 a5 ce ec 23 55 19 a8 ba f6 cd be 63 61 ba 92\n")}), nil),
)
}
if testCase.ec_ro {
gomock.InOrder(
dsc.EXPECT().ExecCommand(gomock.Any(), &rpcMsg{msg: &api.ExecCommandRequest{
Command: "cat",
Args: []string{">", "/var/tmp/someTempDir/futility.py"},
Stdin: []byte(fmt.Sprintf("#!/usr/bin/env python3\n\nimport subprocess\n\n"+
"with open(\"/var/tmp/someTempDir/futility.log\", \"wb\", buffering=0) as outFile:\n"+
" rc = subprocess.run([\"futility\", %s], stdout=outFile, stderr=outFile, check=False, bufsize=0)\n"+
" outFile.write(f\"EXIT CODE: {rc.returncode}\\n\".encode(\"utf-8\"))\n outFile.flush()\n"+
" subprocess.run([\"sync\", \"-d\", \"/var/tmp\", \"/usr/local/tmp\"])\n subprocess.run([\"reboot\"])\n", expectedFutilityPyArgs)),
}}).Return(newResponse(&api.ExecCommandResponse{ExitInfo: &api.ExecCommandResponse_ExitInfo{}}), nil),
dsc.EXPECT().ExecCommand(gomock.Any(), &rpcMsg{msg: &api.ExecCommandRequest{
Command: "bash",
Args: []string{"-c", "'nohup python3 /var/tmp/someTempDir/futility.py </dev/null >&/dev/null & exit'"},
}}).Return(newResponse(&api.ExecCommandResponse{ExitInfo: &api.ExecCommandResponse_ExitInfo{}}), nil),
dsc.EXPECT().ExecCommand(gomock.Any(), &rpcMsg{msg: &api.ExecCommandRequest{
Command: "cat",
Args: []string{"/var/tmp/someTempDir/futility.log"},
}}).Return(newResponse(&api.ExecCommandResponse{ExitInfo: &api.ExecCommandResponse_ExitInfo{}, Stdout: []byte("EXIT CODE: 0\n")}), nil),
dsc.EXPECT().ExecCommand(gomock.Any(), &rpcMsg{msg: &api.ExecCommandRequest{
Command: "rm",
Args: []string{"-f", "/var/tmp/someTempDir/futility.log"},
}}).Return(newResponse(&api.ExecCommandResponse{ExitInfo: &api.ExecCommandResponse_ExitInfo{}}), nil),
)
} else {
gomock.InOrder(
dsc.EXPECT().ExecCommand(gomock.Any(), &rpcMsg{msg: &api.ExecCommandRequest{
Command: "futility",
Args: expectedFutilityArgs,
}}).Return(newResponse(&api.ExecCommandResponse{ExitInfo: &api.ExecCommandResponse_ExitInfo{}}), nil),
)
}
// Execute the state and proceed.
_, _, err := st.Execute(ctx, log)
if err != nil {
t.Fatal(err)
}
st = st.Next()
}
if testCase.updateRw {
// Confirm state name is RW.
checkStateName(st, state_machine.UpdateRwStateName)
// Set mock expectations.
expectedFutilityArgs := []string{"update", "--mode=recovery", "--image=/var/tmp/someTempDir/bios.bin", "--wp=1"}
expectedFutilityManifestArgs := []string{"update", "--manifest", "--image=/var/tmp/someTempDir/bios.bin"}
gomock.InOrder(
dsc.EXPECT().ExecCommand(gomock.Any(), &rpcMsg{msg: &api.ExecCommandRequest{
Command: "futility",
Args: expectedFutilityManifestArgs,
}}).Times(1).Return(newResponse(&api.ExecCommandResponse{ExitInfo: &api.ExecCommandResponse_ExitInfo{}, Stdout: []byte(`{
"default": {
"host": {
"versions": { "ro": "wrongro", "rw": "rwversion" }
}
}
}`)}), nil),
dsc.EXPECT().ExecCommand(gomock.Any(), &rpcMsg{msg: &api.ExecCommandRequest{
Command: "cbfstool",
Args: []string{"/var/tmp/someTempDir/bios.bin", "extract", "-r", "FW_MAIN_A", "-n", "ecrw.hash", "-f", "/var/tmp/someTempDir/bios.bin-ecrw.hash"},
}}).Return(newResponse(&api.ExecCommandResponse{ExitInfo: &api.ExecCommandResponse_ExitInfo{}}), nil),
dsc.EXPECT().ExecCommand(gomock.Any(), &rpcMsg{msg: &api.ExecCommandRequest{
Command: "od",
Args: []string{"-A", "n", "-t", "x1", "/var/tmp/someTempDir/bios.bin-ecrw.hash"},
}}).Return(newResponse(&api.ExecCommandResponse{
ExitInfo: &api.ExecCommandResponse_ExitInfo{}, Stdout: []byte(" 38 73 52 6f 4f f3 af 90 62 68 7c 30 91 13 09 ee\n 12 a5 ce ec 23 55 19 a8 ba f6 cd be 63 61 ba 92\n")}), nil),
dsc.EXPECT().ExecCommand(gomock.Any(), &rpcMsg{msg: &api.ExecCommandRequest{
Command: "futility",
Args: expectedFutilityArgs,
}}).Return(newResponse(&api.ExecCommandResponse{ExitInfo: &api.ExecCommandResponse_ExitInfo{}}), nil),
)
/* OD output:
38 73 52 6f 4f f3 af 90 62 68 7c 30 91 13 09 ee
12 a5 ce ec 23 55 19 a8 ba f6 cd be 63 61 ba 92
ectool output:
status: done
type: SHA-256
offset: 0x00040000
size: 0x00033b80
hash: 3873526f4ff3af9062687c30911309ee12a5ceec235519a8baf6cdbe6361ba92
*/
// Execute the state and proceed.
_, _, err := st.Execute(ctx, log)
if err != nil {
t.Fatal(err)
}
st = st.Next()
}
// Confirm state name is postinstall.
checkStateName(st, state_machine.PostInstallStateName)
// Set mock expectations.
gomock.InOrder(
dsc.EXPECT().ExecCommand(gomock.Any(), &rpcMsg{msg: &api.ExecCommandRequest{
Command: "rm",
Args: []string{"-rf", "/var/tmp/someTempDir"},
}}).Return(newResponse(&api.ExecCommandResponse{ExitInfo: &api.ExecCommandResponse_ExitInfo{}}), nil),
)
if !testCase.ec_ro || testCase.updateRw {
gomock.InOrder(
dsc.EXPECT().Restart(gomock.Any(), gomock.Any()).Return(&longrunning.Operation{Done: true}, nil),
dsc.EXPECT().ExecCommand(gomock.Any(), &rpcMsg{msg: &api.ExecCommandRequest{
Command: "true",
}}).Return(nil, fmt.Errorf("ssh timeout")),
dsc.EXPECT().ExecCommand(gomock.Any(), &rpcMsg{msg: &api.ExecCommandRequest{
Command: "true",
}}).Return(newResponse(&api.ExecCommandResponse{ExitInfo: &api.ExecCommandResponse_ExitInfo{}}), nil),
)
}
if testCase.updateRo && testCase.updateRw {
gomock.InOrder(
dsc.EXPECT().ExecCommand(gomock.Any(), &rpcMsg{msg: &api.ExecCommandRequest{
Command: "crossystem",
Args: []string{"ro_fwid", "fwid"},
}}).Return(newResponse(&api.ExecCommandResponse{
ExitInfo: &api.ExecCommandResponse_ExitInfo{}, Stdout: []byte("roversion rwversion")}), nil),
dsc.EXPECT().ExecCommand(gomock.Any(), &rpcMsg{msg: &api.ExecCommandRequest{
Command: "ectool",
Args: []string{"version"},
}}).Return(newResponse(&api.ExecCommandResponse{
ExitInfo: &api.ExecCommandResponse_ExitInfo{}, Stdout: []byte("RO version: ecroversion\nRW version: ecrwversion\n")}), nil),
dsc.EXPECT().ExecCommand(gomock.Any(), &rpcMsg{msg: &api.ExecCommandRequest{
Command: "ectool",
Args: []string{"echash"},
}}).Return(newResponse(&api.ExecCommandResponse{
ExitInfo: &api.ExecCommandResponse_ExitInfo{}, Stdout: []byte("status: done\ntype: SHA-256\noffset: 0x00040000\nsize: 0x00033b80\nhash: 3873526f4ff3af9062687c30911309ee12a5ceec235519a8baf6cdbe6361ba92\n")}), nil),
)
} else if testCase.updateRo {
gomock.InOrder(
dsc.EXPECT().ExecCommand(gomock.Any(), &rpcMsg{msg: &api.ExecCommandRequest{
Command: "crossystem",
Args: []string{"ro_fwid", "fwid"},
}}).Return(newResponse(&api.ExecCommandResponse{
ExitInfo: &api.ExecCommandResponse_ExitInfo{}, Stdout: []byte("roversion roversioninrw")}), nil),
dsc.EXPECT().ExecCommand(gomock.Any(), &rpcMsg{msg: &api.ExecCommandRequest{
Command: "ectool",
Args: []string{"version"},
}}).Return(newResponse(&api.ExecCommandResponse{
ExitInfo: &api.ExecCommandResponse_ExitInfo{}, Stdout: []byte("RO version: ecroversion\nRW version: ecroversioninrw\n")}), nil),
dsc.EXPECT().ExecCommand(gomock.Any(), &rpcMsg{msg: &api.ExecCommandRequest{
Command: "ectool",
Args: []string{"echash"},
}}).Return(newResponse(&api.ExecCommandResponse{
ExitInfo: &api.ExecCommandResponse_ExitInfo{}, Stdout: []byte("status: done\ntype: SHA-256\noffset: 0x00040000\nsize: 0x00033b80\nhash: 3873526f4ff3af9062687c30911309ee12a5ceec235519a8baf6cdbe6361ba92\n")}), nil),
)
} else if testCase.updateRw {
gomock.InOrder(
dsc.EXPECT().ExecCommand(gomock.Any(), &rpcMsg{msg: &api.ExecCommandRequest{
Command: "crossystem",
Args: []string{"ro_fwid", "fwid"},
}}).Return(newResponse(&api.ExecCommandResponse{
ExitInfo: &api.ExecCommandResponse_ExitInfo{}, Stdout: []byte("roversion rwversion")}), nil),
dsc.EXPECT().ExecCommand(gomock.Any(), &rpcMsg{msg: &api.ExecCommandRequest{
Command: "ectool",
Args: []string{"version"},
}}).Return(newResponse(&api.ExecCommandResponse{
ExitInfo: &api.ExecCommandResponse_ExitInfo{}, Stdout: []byte("RO version: ecroversion\nRW version: ecrwversion\n")}), nil),
dsc.EXPECT().ExecCommand(gomock.Any(), &rpcMsg{msg: &api.ExecCommandRequest{
Command: "ectool",
Args: []string{"echash"},
}}).Return(newResponse(&api.ExecCommandResponse{
ExitInfo: &api.ExecCommandResponse_ExitInfo{}, Stdout: []byte("status: done\ntype: SHA-256\noffset: 0x00040000\nsize: 0x00033b80\nhash: 3873526f4ff3af9062687c30911309ee12a5ceec235519a8baf6cdbe6361ba92\n")}), nil),
)
}
// Execute the state and proceed.
response, _, err := st.Execute(ctx, log)
if err != nil {
t.Fatal(err)
}
st = st.Next()
// Confirm no states left.
checkStateName(st, "")
// Check for missing mock calls.
ctrl.Finish()
if response == nil {
response = fws.GetVersions()
}
if !proto.Equal(response, testCase.expectedResponse) {
t.Fatalf("Expected response, got %s, want %s", response.String(), testCase.expectedResponse.String())
}
}
}