blob: 4b48e72734f368216bd317d2f42cb3f510c99650 [file] [log] [blame] [edit]
// Copyright 2019 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 crash
import (
"compress/gzip"
"context"
"io"
"io/ioutil"
"os"
"path/filepath"
"strings"
"time"
"chromiumos/tast/common/testexec"
"chromiumos/tast/errors"
"chromiumos/tast/local/crash"
"chromiumos/tast/shutil"
"chromiumos/tast/testing"
)
const (
systemCrashDir = "/var/spool/crash"
)
func init() {
testing.AddTest(&testing.Test{
Func: Udev,
Desc: "Verify udev triggered crash works as expected",
Contacts: []string{"yamaguchi@chromium.org", "iby@chromium.org", "cros-telemetry@google.com"},
Attr: []string{"group:mainline"},
})
}
// readLog reads file given by filename, possibly decoding gzip.
func readLog(filename string) ([]byte, error) {
f, err := os.Open(filename)
if err != nil {
return nil, err
}
defer f.Close()
var r io.Reader = f
if strings.HasSuffix(filename, ".gz") {
r, err = gzip.NewReader(f)
if err != nil {
return nil, err
}
}
return ioutil.ReadAll(r)
}
func checkFakeCrashes(pastCrashes map[string]struct{}) (bool, error) {
files, err := ioutil.ReadDir(systemCrashDir)
if err != nil {
if os.IsNotExist(err) {
return false, nil
}
return false, err
}
for _, file := range files {
filename := file.Name()
if _, found := pastCrashes[filename]; found {
continue
}
if !strings.HasPrefix(filename, "__tast_udev_crash_test.") {
continue
}
if !strings.HasSuffix(filename, ".log") && !strings.HasSuffix(filename, ".log.gz") {
continue
}
b, err := readLog(filepath.Join(systemCrashDir, filename))
if err != nil {
// Content error of .gz file (e.g. Unexpected EOF) can happen when
// the file has not been written to the end. Skip it so that the
// file can be visited again by the polling.
continue
}
if string(b) != "ok\n" {
continue
}
return true, nil
}
return false, nil
}
func Udev(ctx context.Context, s *testing.State) {
if err := crash.SetUpCrashTest(ctx, crash.WithMockConsent()); err != nil {
s.Fatal("SetUpCrashTest failed: ", err)
}
defer crash.TearDownCrashTest(ctx)
// Memorize existing crash report to distinguish new reports from them.
files, err := ioutil.ReadDir(systemCrashDir)
pastCrashes := make(map[string]struct{})
if err != nil && !os.IsNotExist(err) {
s.Fatal("Failed to read system crash dir: ", err)
}
for _, file := range files {
pastCrashes[file.Name()] = struct{}{}
}
// Use udevadm to trigger a test-only udev event representing driver failure.
// See 99-crash-reporter.rules for the matcing udev rule.
s.Log("Triggering a fake crash event via udev")
for _, args := range [][]string{
{"udevadm", "control", "--property=TAST_UDEV_TEST=crash"},
{"udevadm", "trigger", "-p", "DEVNAME=/dev/mapper/control"},
{"udevadm", "control", "--property=TAST_UDEV_TEST="},
} {
if err := testexec.CommandContext(ctx, args[0], args[1:]...).Run(); err != nil {
s.Fatalf("%s failed: %v", shutil.EscapeSlice(args), err)
}
}
s.Log("Waiting for the corresponding crash report")
// Check proper crash reports are created.
err = testing.Poll(ctx, func(c context.Context) error {
found, err := checkFakeCrashes(pastCrashes)
if err != nil {
s.Fatal("Failed while polling crash log: ", err)
}
if !found {
return errors.New("no fake crash found")
}
return nil
}, &testing.PollOptions{Timeout: 60 * time.Second})
if err != nil {
s.Error("Failed to wait for crash reports: ", err)
}
}