/
fixture.go
562 lines (496 loc) · 16.3 KB
/
fixture.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
// Copyright 2021 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Package cca provides utilities to interact with Chrome Camera App.
package cca
import (
"context"
"os"
"path/filepath"
"time"
"chromiumos/tast/common/android/ui"
"chromiumos/tast/common/camera/chart"
"chromiumos/tast/ctxutil"
"chromiumos/tast/errors"
"chromiumos/tast/fsutil"
"chromiumos/tast/local/arc"
"chromiumos/tast/local/assistant"
"chromiumos/tast/local/camera/testutil"
"chromiumos/tast/local/chrome"
"chromiumos/tast/local/chrome/browser"
"chromiumos/tast/local/chrome/lacros"
"chromiumos/tast/local/chrome/lacros/lacrosfixt"
"chromiumos/tast/ssh"
"chromiumos/tast/testing"
)
const (
ccaSetUpTimeout = 25 * time.Second
ccaTearDownTimeout = 5 * time.Second
testBridgeSetUpTimeout = 20 * time.Second
setUpTimeout = chrome.LoginTimeout + testBridgeSetUpTimeout
tearDownTimeout = chrome.ResetTimeout
)
type feature string
const (
manualCrop feature = "CameraAppDocumentManualCrop"
)
func init() {
testing.AddFixture(&testing.Fixture{
Name: "ccaLaunched",
Desc: "Launched CCA",
Contacts: []string{"wtlee@chromium.org"},
Data: []string{"cca_ui.js"},
Impl: &fixture{launchCCA: true},
SetUpTimeout: setUpTimeout,
ResetTimeout: testBridgeSetUpTimeout,
PreTestTimeout: ccaSetUpTimeout,
PostTestTimeout: ccaTearDownTimeout,
TearDownTimeout: tearDownTimeout,
})
testing.AddFixture(&testing.Fixture{
Name: "ccaLaunchedGuest",
Desc: "Launched CCA",
Contacts: []string{"wtlee@chromium.org"},
Data: []string{"cca_ui.js"},
Impl: &fixture{guestMode: true, launchCCA: true},
SetUpTimeout: setUpTimeout,
ResetTimeout: testBridgeSetUpTimeout,
PreTestTimeout: ccaSetUpTimeout,
PostTestTimeout: ccaTearDownTimeout,
TearDownTimeout: tearDownTimeout,
})
testing.AddFixture(&testing.Fixture{
Name: "ccaLaunchedWithFakeCamera",
Desc: "Launched CCA with fake camera input",
Contacts: []string{"wtlee@chromium.org"},
Data: []string{"cca_ui.js"},
Impl: &fixture{fakeCamera: true, launchCCA: true},
SetUpTimeout: setUpTimeout,
ResetTimeout: testBridgeSetUpTimeout,
PreTestTimeout: ccaSetUpTimeout,
PostTestTimeout: ccaTearDownTimeout,
TearDownTimeout: tearDownTimeout,
})
testing.AddFixture(&testing.Fixture{
Name: "ccaTestBridgeReady",
Desc: "Set up test bridge for CCA",
Contacts: []string{"wtlee@chromium.org"},
Data: []string{"cca_ui.js"},
Impl: &fixture{},
SetUpTimeout: setUpTimeout,
ResetTimeout: testBridgeSetUpTimeout,
TearDownTimeout: tearDownTimeout,
})
testing.AddFixture(&testing.Fixture{
Name: "ccaTestBridgeReadyLacros",
Desc: "Set up test bridge for CCA",
Contacts: []string{"wtlee@chromium.org"},
Data: []string{"cca_ui.js"},
Impl: &fixture{lacros: true},
SetUpTimeout: setUpTimeout,
ResetTimeout: testBridgeSetUpTimeout,
TearDownTimeout: tearDownTimeout,
Vars: []string{"lacrosDeployedBinary"},
})
testing.AddFixture(&testing.Fixture{
Name: "ccaTestBridgeReadyWithFakeCamera",
Desc: `Set up test bridge for CCA with fake camera. Any tests using this
fixture should switch the camera scene before opening camera`,
Contacts: []string{"wtlee@chromium.org"},
Data: []string{"cca_ui.js"},
Impl: &fixture{fakeCamera: true, fakeScene: true},
SetUpTimeout: setUpTimeout,
ResetTimeout: testBridgeSetUpTimeout,
TearDownTimeout: tearDownTimeout,
})
testing.AddFixture(&testing.Fixture{
Name: "ccaTestBridgeReadyWithFakeCameraLacros",
Desc: `Set up test bridge for CCA with fake camera. Any tests using this
fixture should switch the camera scene before opening camera`,
Contacts: []string{"wtlee@chromium.org"},
Data: []string{"cca_ui.js"},
Impl: &fixture{fakeCamera: true, fakeScene: true, lacros: true},
SetUpTimeout: setUpTimeout,
ResetTimeout: testBridgeSetUpTimeout,
TearDownTimeout: tearDownTimeout,
Vars: []string{"lacrosDeployedBinary"},
})
testing.AddFixture(&testing.Fixture{
Name: "ccaTestBridgeReadyBypassPermissionClamshell",
Desc: "Set up test bridge for CCA with bypassPermission on clamshell mode on",
Contacts: []string{"wtlee@chromium.org"},
Data: []string{"cca_ui.js"},
Impl: &fixture{bypassPermission: true, forceClamshell: true},
SetUpTimeout: setUpTimeout,
ResetTimeout: testBridgeSetUpTimeout,
TearDownTimeout: tearDownTimeout,
})
testing.AddFixture(&testing.Fixture{
Name: "ccaTestBridgeReadyWithArc",
Desc: "Set up test bridge for CCA with ARC enabled",
Contacts: []string{"wtlee@chromium.org"},
Data: []string{"cca_ui.js"},
Impl: &fixture{arcBooted: true},
SetUpTimeout: setUpTimeout + arc.BootTimeout + ui.StartTimeout,
ResetTimeout: testBridgeSetUpTimeout,
TearDownTimeout: tearDownTimeout,
})
testing.AddFixture(&testing.Fixture{
Name: "ccaTestBridgeReadyForDocumentManualCrop",
Desc: "Set up test bridge for CCA and chrome for testing document manual crop",
Contacts: []string{"inker@chromium.org"},
Data: []string{"cca_ui.js"},
Impl: &fixture{
fakeCamera: true,
fakeScene: true,
features: []feature{manualCrop},
},
SetUpTimeout: setUpTimeout,
ResetTimeout: testBridgeSetUpTimeout,
TearDownTimeout: tearDownTimeout,
})
}
// DebugParams defines some useful flags for debug CCA tests.
type DebugParams struct {
SaveScreenshotWhenFail bool
SaveCameraFolderWhenFail bool
}
// TestWithAppParams defines parameters to control behaviors of running test
// with app.
type TestWithAppParams struct {
StopAppOnlyIfExist bool
}
// ResetChromeFunc reset chrome used in this fixture.
type ResetChromeFunc func(context.Context) error
// StartAppFunc starts CCA.
type StartAppFunc func(context.Context) (*App, error)
// StopAppFunc stops CCA.
type StopAppFunc func(context.Context, bool) error
// ResetTestBridgeFunc resets the test bridge.
type ResetTestBridgeFunc func(context.Context) error
// TestWithAppFunc is the function to run with app.
type TestWithAppFunc func(context.Context, *App) error
// FixtureData is the struct exposed to tests.
type FixtureData struct {
Chrome *chrome.Chrome
BrowserType browser.Type
ARC *arc.ARC
TestBridge func() *testutil.TestBridge
// App returns the CCA instance which lives through the test.
App func() *App
// ResetChrome resets chrome used by this fixture.
ResetChrome ResetChromeFunc
// StartApp starts CCA which can be used between subtests.
StartApp StartAppFunc
// StopApp stops CCA which can be used between subtests.
StopApp StopAppFunc
// ResetTestBridgeFunc resets the test bridge. Usually we don't need to call
// it explicitly unless the sub test launch/tear-down the app itself.
ResetTestBridge ResetTestBridgeFunc
// SwitchScene switches the camera scene to the given scene. This only works
// for fixtures using fake camera stream.
SwitchScene func(string) error
// RunTestWithApp runs the given function with the handling of the app
// start/stop.
RunTestWithApp func(context.Context, TestWithAppFunc, TestWithAppParams) error
// PrepareChart prepares chart by loading the given scene. It only works for
// CameraBox.
PrepareChart func(ctx context.Context, addr, keyFile, contentPath string) error
// SetDebugParams sets the debug parameters for current test.
SetDebugParams func(params DebugParams)
}
type fixture struct {
cr *chrome.Chrome
arc *arc.ARC
tb *testutil.TestBridge
app *App
outDir string
chart *chart.Chart
cameraScene string
lacros bool
scriptPaths []string
fakeCamera bool
fakeScene bool
arcBooted bool
launchCCA bool
bypassPermission bool
forceClamshell bool
guestMode bool
debugParams DebugParams
features []feature
}
func (f *fixture) cameraType() testutil.UseCameraType {
if f.fakeCamera {
return testutil.UseFakeCamera
}
return testutil.UseRealCamera
}
func (f *fixture) SetUp(ctx context.Context, s *testing.FixtState) interface{} {
success := false
var chromeOpts []chrome.Option
for _, f := range f.features {
chromeOpts = append(chromeOpts, chrome.EnableFeatures(string(f)))
}
if f.fakeCamera {
chromeOpts = append(chromeOpts, chrome.ExtraArgs(
// The default fps of fake device is 20, but CCA requires fps >= 24.
// Set the fps to 30 to avoid OverconstrainedError.
"--use-fake-device-for-media-stream=fps=30"))
if f.fakeScene {
dataDir := filepath.Dir(s.DataPath("cca_ui.js"))
f.cameraScene = filepath.Join(dataDir, "camera_scene.mjpeg")
chromeOpts = append(chromeOpts, chrome.ExtraArgs(
// Set the default camera scene as the input of the fake stream.
// The content of the scene can be dynamically changed during tests.
"--use-file-for-fake-video-capture="+f.cameraScene))
}
}
if f.guestMode {
chromeOpts = append(chromeOpts, chrome.GuestLogin())
}
if f.arcBooted {
chromeOpts = append(chromeOpts, chrome.ARCEnabled(), chrome.ExtraArgs("--disable-features=ArcResizeLock"))
}
if f.bypassPermission {
chromeOpts = append(chromeOpts, chrome.ExtraArgs("--use-fake-ui-for-media-stream"))
}
if f.forceClamshell {
chromeOpts = append(chromeOpts, chrome.ExtraArgs("--force-tablet-mode=clamshell"))
}
// Enable assistant verbose logging for the CCAUIAssistant test. Since
// assistant is disabled by default, this should not affect other tests.
chromeOpts = append(chromeOpts, assistant.VerboseLogging())
browserType := browser.TypeAsh
if f.lacros {
browserType = browser.TypeLacros
var err error
chromeOpts, err = lacrosfixt.NewConfigFromState(s, lacrosfixt.Mode(lacros.LacrosPrimary), lacrosfixt.ChromeOptions(chromeOpts...)).Opts()
if err != nil {
s.Fatal("Failed to compute Chrome options: ", err)
}
}
cr, err := chrome.New(ctx, chromeOpts...)
if err != nil {
s.Fatal("Failed to start Chrome: ", err)
}
f.cr = cr
defer func() {
if !success {
f.cr.Close(ctx)
f.cr = nil
}
}()
if f.arcBooted {
a, err := arc.New(ctx, s.OutDir())
if err != nil {
s.Fatal("Failed to start ARC: ", err)
}
f.arc = a
defer func() {
if !success {
f.arc.Close(ctx)
f.arc = nil
}
}()
}
tb, err := testutil.NewTestBridge(ctx, cr, f.cameraType())
if err != nil {
s.Fatal("Failed to construct test bridge: ", err)
}
f.tb = tb
f.scriptPaths = []string{s.DataPath("cca_ui.js")}
success = true
return FixtureData{
Chrome: f.cr,
BrowserType: browserType,
ARC: f.arc,
TestBridge: f.testBridge,
App: f.cca,
ResetChrome: f.resetChrome,
StartApp: f.startApp,
StopApp: f.stopApp,
ResetTestBridge: f.resetTestBridge,
SwitchScene: f.switchScene,
RunTestWithApp: f.runTestWithApp,
PrepareChart: f.prepareChart,
SetDebugParams: f.setDebugParams,
}
}
func (f *fixture) TearDown(ctx context.Context, s *testing.FixtState) {
if err := f.tb.TearDown(ctx); err != nil {
s.Error("Failed to tear down test bridge: ", err)
}
f.tb = nil
if f.arcBooted {
if err := f.arc.Close(ctx); err != nil {
s.Error("Failed to tear down ARC: ", err)
}
f.arc = nil
}
if err := f.cr.Close(ctx); err != nil {
s.Error("Failed to tear down Chrome: ", err)
}
f.cr = nil
if f.cameraScene != "" {
if err := os.RemoveAll(f.cameraScene); err != nil {
s.Error("Failed to remove camera scene: ", err)
}
f.cameraScene = ""
}
}
func (f *fixture) Reset(ctx context.Context) error {
return f.resetTestBridge(ctx)
}
func (f *fixture) PreTest(ctx context.Context, s *testing.FixtTestState) {
f.outDir = s.OutDir()
if f.launchCCA {
app, err := f.startApp(ctx)
if err != nil {
s.Fatal("Failed to start app: ", err)
}
f.app = app
}
}
func (f *fixture) PostTest(ctx context.Context, s *testing.FixtTestState) {
defer func() {
f.debugParams = DebugParams{}
}()
if f.chart != nil {
if err := f.chart.Close(ctx, f.outDir); err != nil {
s.Error("Failed to close chart: ", err)
}
f.chart = nil
}
if f.launchCCA {
if err := f.stopApp(ctx, s.HasError()); err != nil {
s.Fatal("Failed to stop app: ", err)
}
}
}
func (f *fixture) resetTestBridge(ctx context.Context) error {
if err := f.tb.TearDown(ctx); err != nil {
return errors.Wrap(err, "failed to tear down test bridge")
}
f.tb = nil
cameraType := testutil.UseRealCamera
if f.fakeCamera {
cameraType = testutil.UseFakeCamera
}
tb, err := testutil.NewTestBridge(ctx, f.cr, cameraType)
if err != nil {
return errors.Wrap(err, "failed to construct test bridge")
}
f.tb = tb
return nil
}
func (f *fixture) resetChrome(ctx context.Context) error {
if err := f.cr.ResetState(ctx); err != nil {
return errors.Wrap(err, "failed to reset chrome in fixture")
}
tb, err := testutil.NewTestBridge(ctx, f.cr, f.cameraType())
if err != nil {
return errors.Wrap(err, "failed to construct test bridge after reset chrome state")
}
f.tb = tb
return nil
}
func (f *fixture) startApp(ctx context.Context) (*App, error) {
if err := ClearSavedDir(ctx, f.cr); err != nil {
return nil, errors.Wrap(err, "failed to clear camera folder")
}
app, err := New(ctx, f.cr, f.scriptPaths, f.outDir, f.tb)
if err != nil {
return nil, errors.Wrap(err, "failed to open CCA")
}
f.app = app
return f.app, nil
}
func (f *fixture) stopApp(ctx context.Context, hasError bool) (retErr error) {
if f.app == nil {
return
}
defer func(ctx context.Context) {
if err := f.app.CloseWithDebugParams(ctx, f.debugParams); err != nil {
retErr = errors.Wrap(retErr, err.Error())
}
f.app = nil
}(ctx)
if hasError && f.debugParams.SaveScreenshotWhenFail {
if err := f.app.SaveScreenshot(ctx); err != nil {
return errors.Wrap(err, "failed to save a screenshot")
}
}
return nil
}
func (f *fixture) stopAppIfExist(ctx context.Context, hasError bool) error {
if appExist, err := InstanceExists(ctx, f.cr); err != nil {
return errors.Wrap(err, "failed to check existence of CCA")
} else if appExist {
return f.stopApp(ctx, hasError)
} else {
// The app does not exist, might be proactively closed by the test flow.
f.app = nil
}
return nil
}
// switchScene switches the camera scene of fake camera to the given |scene|.
func (f *fixture) switchScene(scene string) error {
if f.cameraScene == "" {
return errors.New("failed to switch scene for non-fake camera stream")
}
if err := fsutil.CopyFile(scene, f.cameraScene); err != nil {
return errors.Wrapf(err, "failed to copy from the given scene: %v", scene)
}
return nil
}
func (f *fixture) runTestWithApp(ctx context.Context, testFunc TestWithAppFunc, params TestWithAppParams) (retErr error) {
app, err := f.startApp(ctx)
if err != nil {
return errors.Wrap(err, "failed to start app")
}
cleanupCtx := ctx
ctx, cancel := ctxutil.Shorten(ctx, 3*time.Second)
defer cancel()
defer f.resetTestBridge(cleanupCtx)
defer func(cleanupCtx context.Context) {
hasError := retErr != nil
stopFunc := f.stopApp
if params.StopAppOnlyIfExist {
stopFunc = f.stopAppIfExist
}
if err := stopFunc(cleanupCtx, hasError); err != nil {
retErr = errors.Wrap(retErr, err.Error())
}
}(cleanupCtx)
return testFunc(ctx, app)
}
func (f *fixture) prepareChart(ctx context.Context, addr, keyFile, contentPath string) (retErr error) {
var sopt ssh.Options
ssh.ParseTarget(addr, &sopt)
sopt.KeyFile = keyFile
sopt.ConnectTimeout = 10 * time.Second
conn, err := ssh.New(ctx, &sopt)
if err != nil {
return errors.Wrap(err, "failed to connect to chart tablet")
}
// No need to close ssh connection since chart will handle it when cleaning
// up.
c, namePaths, err := chart.SetUp(ctx, conn, f.outDir, []string{contentPath})
if err != nil {
return errors.Wrap(err, "failed to prepare chart tablet")
}
if err := c.Display(ctx, namePaths[0]); err != nil {
return errors.Wrap(err, "failed to display chart on chart tablet")
}
f.chart = c
return nil
}
func (f *fixture) testBridge() *testutil.TestBridge {
return f.tb
}
func (f *fixture) cca() *App {
return f.app
}
func (f *fixture) setDebugParams(params DebugParams) {
f.debugParams = params
}