blob: f2654dc24315dede42e856e759ee79ce34543d1f [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 (
"context"
"fmt"
"io"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"strings"
"time"
"github.com/shirou/gopsutil/host"
commoncrash "chromiumos/tast/common/crash"
"chromiumos/tast/common/testexec"
"chromiumos/tast/ctxutil"
"chromiumos/tast/errors"
"chromiumos/tast/local/bundles/cros/crash/crash"
"chromiumos/tast/local/chrome"
localcrash "chromiumos/tast/local/crash"
"chromiumos/tast/local/syslog"
"chromiumos/tast/local/upstart"
"chromiumos/tast/shutil"
"chromiumos/tast/testing"
)
const (
leaveCorePath = "/root/.leave_core"
crashReporterEnabledPath = "/var/lib/crash_reporter/crash-handling-enabled"
)
type userCrashParams struct {
testFunc func(context.Context, *chrome.Chrome, *testing.State)
consentType localcrash.ConsentType
}
func init() {
testing.AddTest(&testing.Test{
Func: User,
Desc: "Verifies crash reporting for user processes",
Contacts: []string{
"domlaskowski@chromium.org", // Original autotest author
"yamaguchi@chromium.org", // Tast port author
"cros-telemetry@google.com",
},
Attr: []string{"group:mainline"},
// Some of these tests verify crash_sender behavior, so only run on internal builds.
SoftwareDeps: []string{"cros_internal"},
Params: []testing.Param{{
Name: "reporter_startup",
Val: userCrashParams{
testFunc: testReporterStartup,
consentType: localcrash.MockConsent,
},
}, {
Name: "core_file_removed_in_production",
Val: userCrashParams{
testFunc: testCoreFileRemovedInProduction,
consentType: localcrash.MockConsent,
},
}, {
Name: "reporter_shutdown",
Val: userCrashParams{
testFunc: testReporterShutdown,
consentType: localcrash.MockConsent,
},
}, {
Name: "no_crash",
Val: userCrashParams{
testFunc: testNoCrash,
consentType: localcrash.MockConsent,
},
}, {
Name: "chronos_crasher_real_consent",
Val: userCrashParams{
testFunc: testChronosCrasher,
consentType: localcrash.RealConsent,
},
ExtraSoftwareDeps: []string{"chrome", "metrics_consent"},
}, {
Name: "chronos_crasher_mock_consent",
Val: userCrashParams{
testFunc: testChronosCrasher,
consentType: localcrash.MockConsent,
},
}, {
Name: "chronos_crasher_no_consent",
Val: userCrashParams{
testFunc: testChronosCrasherNoConsent,
consentType: localcrash.RealConsent,
},
ExtraSoftwareDeps: []string{"chrome", "metrics_consent"},
}, {
Name: "root_crasher_real_consent",
Val: userCrashParams{
testFunc: testRootCrasher,
consentType: localcrash.RealConsent,
},
ExtraSoftwareDeps: []string{"chrome", "metrics_consent"},
}, {
Name: "root_crasher_mock_consent",
Val: userCrashParams{
testFunc: testRootCrasher,
consentType: localcrash.MockConsent,
},
}, {
Name: "root_crasher_no_consent",
Val: userCrashParams{
testFunc: testRootCrasherNoConsent,
consentType: localcrash.RealConsent,
},
ExtraSoftwareDeps: []string{"chrome", "metrics_consent"},
}, {
Name: "crash_filtering",
Val: userCrashParams{
testFunc: testCrashFiltering,
consentType: localcrash.MockConsent,
},
}, {
Name: "crash_blocking",
Val: userCrashParams{
testFunc: testCrashBlocking,
consentType: localcrash.MockConsent,
},
}, {
Name: "max_enqueued_crash",
Val: userCrashParams{
testFunc: testMaxEnqueuedCrash,
consentType: localcrash.MockConsent,
},
}, {
Name: "core2md_failure",
Val: userCrashParams{
testFunc: testCore2mdFailure,
consentType: localcrash.MockConsent,
},
}, {
Name: "internal_directory_failure",
Val: userCrashParams{
testFunc: testInternalDirectoryFailure,
consentType: localcrash.MockConsent,
},
}, {
Name: "crash_logs_creation",
Val: userCrashParams{
testFunc: testCrashLogsCreation,
consentType: localcrash.MockConsent,
},
}, {
Name: "crash_log_infinite_recursion",
Val: userCrashParams{
testFunc: testCrashLogInfiniteRecursion,
consentType: localcrash.MockConsent,
},
}},
})
}
// testReporterStartup tests that the core_pattern is set up by crash reporter.
func testReporterStartup(ctx context.Context, cr *chrome.Chrome, s *testing.State) {
// Turn off crash filtering so we see the original setting.
if err := localcrash.DisableCrashFiltering(); err != nil {
s.Error("Failed to turn off crash filtering: ", err)
return
}
out, err := ioutil.ReadFile(commoncrash.CorePattern)
if err != nil {
s.Error("Failed to read core pattern file: ", commoncrash.CorePattern)
return
}
trimmed := strings.TrimSuffix(string(out), "\n")
if expected := commoncrash.ExpectedCorePattern(); trimmed != expected {
s.Errorf("Unexpected core_pattern: got %s, want %s", trimmed, expected)
}
// Check that we wrote out the file indicating that crash_reporter is
// enabled AFTER the system was booted. This replaces the old technique
// of looking for the log message which was flakey when the logs got
// flooded.
// NOTE: This technique doesn't need to be highly accurate, we are only
// verifying that the flag was written after boot and there are multiple
// seconds between those steps, and a file from a prior boot will almost
// always have been written out much further back in time than our
// current boot time.
f, err := os.Stat(crashReporterEnabledPath)
if err != nil || !f.Mode().IsRegular() {
s.Error("Crash reporter enabled file flag is not present at ", crashReporterEnabledPath)
return
}
flagTime := time.Since(f.ModTime())
uptimeSeconds, err := host.Uptime()
if err != nil {
s.Error("Failed to get uptime: ", err)
return
}
if flagTime > time.Duration(uptimeSeconds)*time.Second {
s.Error("User space crash handling was not started during last boot")
}
}
func unstashLeaveCore(ctx context.Context) {
if err := testexec.CommandContext(ctx, "umount", "/root").Run(testexec.DumpLogOnError); err != nil {
testing.ContextLog(ctx, "Failed to unmount: ", err)
}
}
// stashLeaveCore prepares /root directory with .leave_core file eliminated.
// The first return value indicates whether the stashing happened, regardless
// if there was error after that. When it's true, the caller should call
// unstashLeaveCore() to resume the original /root directory.
func stashLeaveCore(ctx context.Context, cr *chrome.Chrome, s *testing.State) (retErr error) {
fullCtx := ctx
ctx, cancel := ctxutil.Shorten(fullCtx, 10*time.Second)
defer cancel()
// Avoid remounting / rw by instead creating a tmpfs in /root and
// populating it with everything but the .leave_core file.
for _, args := range [][]string{
{"tar", "-cz", "-C", "/root", "-f", "/tmp/root.tgz", "."},
{"mount", "-t", "tmpfs", "tmpfs", "/root"},
} {
if err := testexec.CommandContext(ctx, args[0], args[1:]...).Run(); err != nil {
return errors.Wrapf(err, "%s failed", shutil.EscapeSlice(args))
}
}
defer func() {
if retErr != nil {
unstashLeaveCore(fullCtx)
}
}()
args := []string{"tar", "-xz", "-C", "/root", "-f", "/tmp/root.tgz", "."}
if err := testexec.CommandContext(ctx, args[0], args[1:]...).Run(); err != nil {
return errors.Wrapf(err, "%s failed", shutil.EscapeSlice(args))
}
if err := os.Remove("/tmp/root.tgz"); err != nil {
return err
}
// /root/.leave_core always exists in a test image.
if err := os.Remove(leaveCorePath); err != nil {
return err
}
return nil
}
// testCoreFileRemovedInProduction tests core files do not stick around for production builds.
func testCoreFileRemovedInProduction(ctx context.Context, cr *chrome.Chrome, s *testing.State) {
fullCtx := ctx
ctx, cancel := ctxutil.Shorten(fullCtx, 10*time.Second)
defer cancel()
if err := stashLeaveCore(ctx, cr, s); err != nil {
s.Fatal("Failed to stash .leave_core: ", err)
}
defer unstashLeaveCore(fullCtx)
reader, err := syslog.NewReader(ctx, syslog.Program(syslog.CrashReporter))
if err != nil {
s.Fatal("Failed to create log reader: ", err)
}
defer reader.Close()
opts := crash.DefaultCrasherOptions()
opts.Username = "root"
if result, err := crash.RunCrasherProcess(ctx, cr, opts); err != nil {
s.Fatal("Failed to run crasher process: ", err)
} else if !result.Crashed {
s.Fatal("Crasher did not crash")
}
crashDir, err := localcrash.GetCrashDir("root")
if err != nil {
s.Fatal("Failed opening root crash dir: ", err)
}
files, err := ioutil.ReadDir(crashDir)
if err != nil {
s.Fatal("Failed to read crash dir: ", err)
}
var crashContents []string
for _, f := range files {
crashContents = append(crashContents, f.Name())
}
testing.ContextLog(ctx, "Contents of crash directory: ", crashContents)
const leavingCore = "Leaving core file at"
for {
e, err := reader.Read()
if err == io.EOF {
break
}
if err != nil {
s.Fatal("Failed to read syslog: ", err)
}
if strings.Contains(e.Content, leavingCore) {
s.Errorf("Unexpected log message %q", leavingCore)
}
}
dmpFiles := 0
for _, n := range crashContents {
if strings.HasSuffix(n, ".dmp") {
dmpFiles++
} else if strings.HasSuffix(n, ".core") {
s.Error("Unexpected core file found: ", n)
}
}
if dmpFiles != 1 {
s.Errorf("Got %d dmp files, want 1", dmpFiles)
}
if err := crash.CleanCrashSpoolDirs(ctx, crash.CrasherPath); err != nil {
s.Error("Failed to clean crash spool dirs: ", err)
}
}
// testReporterShutdown tests the crash_reporter shutdown code works.
func testReporterShutdown(ctx context.Context, cr *chrome.Chrome, s *testing.State) {
cmd := testexec.CommandContext(ctx, commoncrash.CrashReporterPath, "--clean_shutdown")
if err := cmd.Run(testexec.DumpLogOnError); err != nil {
s.Error("Failed to clean shutdown crash reporter: ", err)
}
b, err := ioutil.ReadFile(commoncrash.CorePattern)
if err != nil {
s.Error("Failed to read core pattern file")
}
output := strings.TrimSpace(string(b))
if output != "core" {
s.Errorf("Core pattern was %q; want \"core\"", output)
}
}
// testNoCrash tests that crasher can exit normally.
func testNoCrash(ctx context.Context, cr *chrome.Chrome, s *testing.State) {
opts := crash.DefaultCrasherOptions()
opts.Username = "root"
opts.CauseCrash = false
result, err := crash.RunCrasherProcessAndAnalyze(ctx, cr, opts)
if err != nil {
s.Error("testNoCrash failed: ", err)
return
}
if result.Crashed || result.CrashReporterCaught || result.ReturnCode != 0 {
s.Error("testNoCrash failed: not expecting crash")
}
}
// testChronosCrasher tests that crasher exits by SIGSEGV with user "chronos".
func testChronosCrasher(ctx context.Context, cr *chrome.Chrome, s *testing.State) {
opts := crash.DefaultCrasherOptions()
opts.Username = "chronos"
if err := crash.CheckCrashingProcess(ctx, cr, opts); err != nil {
s.Error("testChronosCrasher failed: ", err)
}
if err := crash.CleanCrashSpoolDirs(ctx, crash.CrasherPath); err != nil {
s.Error("Failed to clean crash spool dirs: ", err)
}
}
// testChronosCrasherNoConsent tests that no files are stored without consent, with user "chronos".
func testChronosCrasherNoConsent(ctx context.Context, cr *chrome.Chrome, s *testing.State) {
if err := localcrash.SetConsent(ctx, cr, false); err != nil {
s.Fatal("testChronosCrasherNoConsent failed: ", err)
}
opts := crash.DefaultCrasherOptions()
opts.Consent = false
opts.Username = "chronos"
if err := crash.CheckCrashingProcess(ctx, cr, opts); err != nil {
s.Error("testChronosCrasherNoConsent failed: ", err)
}
}
// testRootCrasher tests that crasher exits by SIGSEGV with the root user.
func testRootCrasher(ctx context.Context, cr *chrome.Chrome, s *testing.State) {
opts := crash.DefaultCrasherOptions()
opts.Username = "root"
if err := crash.CheckCrashingProcess(ctx, cr, opts); err != nil {
s.Error("testRootCrasher failed: ", err)
}
if err := crash.CleanCrashSpoolDirs(ctx, crash.CrasherPath); err != nil {
s.Error("Failed to clean crash spool dirs: ", err)
}
}
// testRootCrasherNoConsent tests that no files are stored without consent, with the root user.
func testRootCrasherNoConsent(ctx context.Context, cr *chrome.Chrome, s *testing.State) {
if err := localcrash.SetConsent(ctx, cr, false); err != nil {
s.Fatal("testRootCrasherNoConsent failed: ", err)
}
opts := crash.DefaultCrasherOptions()
opts.Consent = false
opts.Username = "root"
if err := crash.CheckCrashingProcess(ctx, cr, opts); err != nil {
s.Error("testRootCrasherNoConsent failed: ", err)
}
}
// checkFilterCrasher runs crasher and verifies that crash_reporter receives or ignores the crash.
func checkFilterCrasher(ctx context.Context, shouldReceive bool) error {
reader, err := syslog.NewReader(ctx)
if err != nil {
return err
}
defer reader.Close()
cmd := testexec.CommandContext(ctx, crash.CrasherPath)
if err := cmd.Run(testexec.DumpLogOnError); err == nil {
return errors.Wrap(err, "crasher did not crash")
} else if _, ok := err.(*exec.ExitError); !ok {
return errors.Wrap(err, "failed to run crasher")
}
crasherBasename := filepath.Base(crash.CrasherPath)
var expected string
// Verify if crash_reporter received or not using log messages by that program.
// These must be kept in sync with those in UserCollectorBase::HandleCrash in
// src/platform2/crash-reporter/user_collector_base.cc.
if shouldReceive {
expected = "Received crash notification for " + crasherBasename
} else {
expected = fmt.Sprintf("Ignoring crash invocation '--user=%d:11:0:0:%s'", cmd.Process.Pid, crasherBasename)
}
if _, err := reader.Wait(ctx, 10*time.Second, func(e *syslog.Entry) bool {
return strings.Contains(e.Content, expected)
}); err != nil {
return errors.Wrapf(err, "timeout waiting for %s in syslog", expected)
}
// crash_reporter may write log multiple times with different tags for certain message,
// if it runs multiple "collectors" in it. (Currently it has user and ARC collectors.)
// "Ignoring" message doesn't have a tag to simply identify which collector wrote it.
// Wait until those messages are flushed. Otherwise next test will capture them wrongly.
const successLog = "CheckFilterCrasher successfully verified."
testing.ContextLog(ctx, successLog)
if _, err := reader.Wait(ctx, 10*time.Second, func(e *syslog.Entry) bool {
return strings.Contains(e.Content, successLog)
}); err != nil {
return errors.Wrapf(err, "timeout waiting for log flushed: want %q", successLog)
}
if err := crash.CleanCrashSpoolDirs(ctx, crash.CrasherPath); err != nil {
return errors.Wrap(err, "failed to clean crash spool dirs")
}
return nil
}
// testCrashFiltering tests that crash filtering (a feature needed for testing) works.
func testCrashFiltering(ctx context.Context, cr *chrome.Chrome, s *testing.State) {
localcrash.EnableCrashFiltering(ctx, localcrash.FilterInIgnoreAllCrashes)
if err := checkFilterCrasher(ctx, false); err != nil {
s.Error("testCrashFiltering failed for filter=\"none\": ", err)
}
localcrash.EnableCrashFiltering(ctx, "sleep")
if err := checkFilterCrasher(ctx, false); err != nil {
s.Error("testCrashFiltering failed for filter=\"sleep\": ", err)
}
localcrash.DisableCrashFiltering()
if err := checkFilterCrasher(ctx, true); err != nil {
s.Error("testCrashFiltering failed for no-filter: ", err)
}
}
func testCrashBlocking(ctx context.Context, c *chrome.Chrome, s *testing.State) {
// First, disable filter-in.
if err := localcrash.DisableCrashFiltering(); err != nil {
s.Fatal("Failed to disable crash filter: ", err)
}
if err := localcrash.EnableCrashBlocking(ctx, filepath.Base(crash.CrasherPath)); err != nil {
s.Fatal("Failed to enable crash blocking: ", err)
}
if err := checkFilterCrasher(ctx, false); err != nil {
s.Errorf("testCrashBlocking failed for filter=%q: %v", filepath.Base(crash.CrasherPath), err)
}
if err := localcrash.DisableCrashBlocking(); err != nil {
s.Fatal("Failed to disable crash blocking: ", err)
}
if err := checkFilterCrasher(ctx, true); err != nil {
s.Error("testCrashBlocking failed for no-filter: ", err)
}
}
// checkCollectionFailure is a helper function for testing with crash log collection failures.
func checkCollectionFailure(ctx context.Context, cr *chrome.Chrome, testOption, failureString string) error {
// Add parameter to core_pattern.
out, err := ioutil.ReadFile(commoncrash.CorePattern)
if err != nil {
return errors.Wrapf(err, "failed to read core pattern file: %s", commoncrash.CorePattern)
}
oldCorePattern := strings.TrimSpace(string(out))
if err := ioutil.WriteFile(commoncrash.CorePattern, []byte(oldCorePattern+" "+testOption), 0644); err != nil {
return errors.Wrapf(err, "failed to add core pattern: %s", testOption)
}
defer func() {
if err := ioutil.WriteFile(commoncrash.CorePattern, []byte(oldCorePattern), 0644); err != nil {
testing.ContextLog(ctx, "Failed to restore core pattern file: ", err)
}
}()
reader, err := syslog.NewReader(ctx)
if err != nil {
return errors.Wrap(err, "failed to create log reader")
}
defer reader.Close()
opts := crash.DefaultCrasherOptions()
opts.Username = "root"
opts.ExpectCrashReporterFail = true
result, err := crash.RunCrasherProcessAndAnalyze(ctx, cr, opts)
if err != nil {
return errors.Wrap(err, "failed to call crasher")
}
if !result.Crashed {
return errors.Errorf("crasher returned %d instead of crashing", result.ReturnCode)
}
if !result.CrashReporterCaught {
return errors.New("logs do not contain crash_reporter message")
}
// RunCrasherProcessAndAnalyze waits the first line of crash_reporter log appears.
// However, the rest of the log by crash_reporter is written asynchronously.
// Therefore we need Wait here.
if _, err := reader.Wait(ctx, 1*time.Second, func(e *syslog.Entry) bool {
return strings.Contains(e.Content, failureString)
}); err != nil {
return errors.Wrapf(err, "did not find fail string in the log: %s", failureString)
}
if result.Minidump != "" {
return errors.New("failed collection resulted in minidump")
}
if result.Log == "" {
return errors.New("failed collection had no log")
}
out, err = ioutil.ReadFile(result.Log)
if err != nil {
return err
}
logContents := string(out)
if !strings.Contains(logContents, failureString) {
return errors.Errorf("did not find %q in the result log %s", failureString, result.Log)
}
pslogName := result.Pslog
out, err = ioutil.ReadFile(pslogName)
if err != nil {
return err
}
logContents = string(out)
// Verify we are generating appropriate diagnostic output.
if !strings.Contains(logContents, "===ps output===") || !strings.Contains(logContents, "===meminfo===") {
return errors.Errorf("expected full logs in the result log %s", result.Log)
}
// TODO(crbug.com/970930): Check generated report sent.
// The function is to be introduced by crrev.com/c/1906405.
// const collectionErrorSignature = "crash_reporter-user-collection"
// crash.CheckGeneratedReportSending(result.Meta, result.Log, result.Basename, "log", collectionErrorSignature)
if err := crash.CleanCrashSpoolDirs(ctx, crash.CrasherPath); err != nil {
return errors.Wrap(err, "failed to clean crash files")
}
return nil
}
func testCore2mdFailure(ctx context.Context, cr *chrome.Chrome, s *testing.State) {
const core2mdPath = "/usr/bin/core2md"
if err := checkCollectionFailure(ctx, cr, "--core2md_failure", "Problem during "+core2mdPath+" [result=1]"); err != nil {
s.Error("testCore2mdFailure failed: ", err)
}
}
func testInternalDirectoryFailure(ctx context.Context, cr *chrome.Chrome, s *testing.State) {
if err := checkCollectionFailure(ctx, cr, "--directory_failure", "Purposefully failing to create"); err != nil {
s.Error("testInternalDirectoryFailure failed: ", err)
}
}
func testCrashLogsCreation(ctx context.Context, cr *chrome.Chrome, s *testing.State) {
const CrashLogTest = "crash_log_test"
// Copy and rename crasher to trigger crash_reporter_logs.conf rule.
opts := crash.DefaultCrasherOptions()
opts.Username = "root"
opts.CrasherPath = filepath.Join(filepath.Dir(crash.CrasherPath), CrashLogTest)
result, err := crash.RunCrasherProcessAndAnalyze(ctx, cr, opts)
if err != nil {
s.Fatal("Failed to run crasher: ", err)
}
if !result.Crashed {
s.Errorf("Crasher returned %d instead of crashing", result.ReturnCode)
}
if !result.CrashReporterCaught {
s.Error("Logs do not contain crash_reporter message")
}
b, err := ioutil.ReadFile(result.Log)
if err != nil {
s.Error("Failed to read result log: ", err)
}
if contents := string(b); contents != "hello world\n" {
s.Error("Crash log contents unexpected: ", contents)
}
b, err = ioutil.ReadFile(result.Meta)
if err != nil {
s.Error("Failed to read result meta: ", err)
}
if !strings.Contains(string(b), "log="+filepath.Base(result.Log)) {
s.Error("Meta file does not reference log")
}
if err := crash.CleanCrashSpoolDirs(ctx, CrashLogTest); err != nil {
s.Error("Failed to clean crash spool dirs: ", err)
}
}
func testCrashLogInfiniteRecursion(ctx context.Context, cr *chrome.Chrome, s *testing.State) {
// Copy and rename crasher to trigger crash_reporter_logs.conf rule.
bindir := filepath.Dir(crash.CrasherPath)
recursionTriggeringCrasher := filepath.Join(bindir, "crash_log_recursion_tast_test")
// The configuration file hardcodes this path, so make sure it's still the same.
// See /src/platform2/crash-reporter/crash_reporter_logs.conf
const RecursionTestPath = "/usr/local/libexec/tast/helpers/local/cros/crash_log_recursion_tast_test"
if recursionTriggeringCrasher != RecursionTestPath {
s.Fatalf("Path to recursion test changed; want %s, got %s", RecursionTestPath, recursionTriggeringCrasher)
}
// Simply completing this command means that we avoided infinite recursion.
opts := crash.DefaultCrasherOptions()
opts.Username = "root"
opts.CrasherPath = recursionTriggeringCrasher
result, err := crash.RunCrasherProcess(ctx, cr, opts)
if err != nil {
s.Fatal("Failed to run crasher process: ", err)
}
if !result.Crashed {
s.Errorf("Crasher returned %d instead of crashing", result.ReturnCode)
}
if !result.CrashReporterCaught {
s.Error("Logs do not contain crash_reporter message")
}
if err := crash.CleanCrashSpoolDirs(ctx, filepath.Base(RecursionTestPath)); err != nil {
s.Error("Failed to clean crash files: ", err)
}
}
// testMaxEnqueuedCrash tests that the maximum crash directory size is enforced.
func testMaxEnqueuedCrash(ctx context.Context, cr *chrome.Chrome, s *testing.State) {
const (
maxCrashDirectorySize = 32
username = "root"
)
reader, err := syslog.NewReader(ctx)
defer reader.Close()
if err != nil {
s.Fatal("Failed to create watcher: ", err)
}
crashDir, err := localcrash.GetCrashDir(username)
if err != nil {
s.Fatal("Failed before queueing: ", err)
}
fullMessage := fmt.Sprintf("Crash directory %s already full with %d pending reports",
crashDir, maxCrashDirectorySize)
opts := crash.DefaultCrasherOptions()
opts.Username = username
// Fill up the queue.
for i := 0; i < maxCrashDirectorySize; i++ {
result, err := crash.RunCrasherProcess(ctx, cr, opts)
if err != nil {
s.Fatal("Failure while setting up queue: ", err)
}
if !result.Crashed {
s.Fatal("Failure while setting up queue: ", result.ReturnCode)
}
if !result.CrashReporterCaught {
s.Fatal("Crash reporter did not handle while setting up queue")
}
for {
e, err := reader.Read()
if err == io.EOF {
break
}
if err != nil {
s.Fatal("Failed to read syslog: ", err)
}
if strings.Contains(e.Content, fullMessage) {
s.Fatal("Unexpected full message: ", e.Content)
}
}
}
files, err := ioutil.ReadDir(crashDir)
if err != nil {
s.Fatal("Failed to get crash dir size: ", crashDir)
}
crashDirSize := len(files)
testing.ContextLogf(ctx, "Crash directory had %d entries", crashDirSize)
// Crash a bunch more times, but make sure no new reports are enqueued.
for i := 0; i < 10; i++ {
result, err := crash.RunCrasherProcess(ctx, cr, opts)
if err != nil {
s.Fatal("Failure while running crasher after enqueued: ", err)
}
if !result.Crashed {
s.Fatal("Failure after setting up queue: ", result.ReturnCode)
}
if !result.CrashReporterCaught {
s.Fatal("Crash reporter did not catch crash")
}
if _, err := reader.Wait(ctx, 30*time.Second, func(e *syslog.Entry) bool { return strings.Contains(e.Content, fullMessage) }); err != nil {
s.Error("Expected full message: ", fullMessage)
}
files, err = ioutil.ReadDir(crashDir)
if err != nil {
s.Fatalf("Failed to get crash dir size of %s: %v", crashDir, err)
}
if crashDirSize != len(files) {
s.Errorf("Expected no new files (now %d, were %d)", len(files), crashDirSize)
}
}
if err := crash.CleanCrashSpoolDirs(ctx, crash.CrasherPath); err != nil {
s.Error("Failed to clean crash files: ", err)
}
}
func User(ctx context.Context, s *testing.State) {
if err := upstart.RestartJob(ctx, "ui"); err != nil {
s.Fatal("Failed to restart UI job")
}
consentType := s.Param().(userCrashParams).consentType
var cr *chrome.Chrome
if consentType == localcrash.RealConsent {
var err error
cr, err = chrome.New(ctx)
if err != nil {
s.Fatal("Chrome login failed: ", err)
}
defer cr.Close(ctx)
}
f := s.Param().(userCrashParams).testFunc
if err := crash.RunCrashTest(ctx, cr, s, f, consentType); err != nil {
s.Error("Test failed: ", err)
}
}