blob: e6da155b2153cc21b503738a60ef4e3a399644b2 [file] [log] [blame]
// Copyright 2020 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 (
"bytes"
"context"
"encoding/binary"
"io/ioutil"
"os"
"path/filepath"
"github.com/golang/protobuf/proto"
"go.chromium.org/chromiumos/config/go/api/test/tls"
"chromiumos/tast/common/testexec"
"chromiumos/tast/local/crash"
"chromiumos/tast/testing"
)
func init() {
testing.AddTest(&testing.Test{
Func: Serializer,
Desc: "Basic test to check that minidump crashes are serialized",
Contacts: []string{
"mutexlox@chromium.org",
"cros-telemetry@google.com",
},
// We only care about crash_serializer on internal builds.
SoftwareDeps: []string{"cros_internal"},
Params: []testing.Param{{
Name: "",
Val: false,
}, {
Name: "fetch_core",
Val: true,
}},
Attr: []string{"group:mainline"},
})
}
// verifyMetaField verifies that the given meta field is found in the crash's CrashMetadata
func verifyMetaField(ctx context.Context, key, expected string, crash *tls.CrashInfo) bool {
for _, kv := range crash.Fields {
if kv.Key == key {
if expected != kv.Text {
testing.ContextLogf(ctx, "Unexpected value for %s. Want %s, got %s", key, expected, kv.Text)
return false
}
return true
}
}
testing.ContextLogf(ctx, "%s was unexpectedly not present (want %s)", key, expected)
return false
}
func Serializer(ctx context.Context, s *testing.State) {
if err := crash.SetUpCrashTest(ctx, crash.FilterCrashes(crash.FilterInIgnoreAllCrashes), crash.WithMockConsent()); err != nil {
s.Fatal("Setup failed: ", err)
}
defer crash.TearDownCrashTest(ctx)
const (
basename = "some_program.1.2.3.4"
// Coredump should be large enough to need to split into multiple messages
coreBytes = 3 * 1024 * 1024
coreChunks = 3
)
exp, err := crash.AddFakeMinidumpCrash(ctx, basename)
if err != nil {
s.Fatal("Failed to add a fake minidump crash: ", err)
}
// Explicitly clean up created files, since the serializer won't.
defer func() {
if err := os.Remove(exp.MetadataPath); err != nil && !os.IsNotExist(err) {
s.Errorf("Failed to remove %s: %s", exp.MetadataPath, err)
}
if err := os.Remove(exp.PayloadPath); err != nil && !os.IsNotExist(err) {
s.Errorf("Failed to remove %s: %s", exp.PayloadPath, err)
}
}()
coreName := filepath.Join(crash.SystemCrashDir, basename+".core")
addCore := s.Param().(bool)
if addCore {
if err := crash.CreateRandomFile(coreName, coreBytes); err != nil {
s.Fatal("Failed to add a coredump: ", err)
}
defer func() {
if err := os.Remove(coreName); err != nil && !os.IsNotExist(err) {
s.Errorf("Failed to remove %s: %s", coreName, err)
}
}()
}
s.Log("Running crash_serializer")
var args []string
if addCore {
args = append(args, "--fetch_coredumps")
}
cmd := testexec.CommandContext(ctx, "/usr/local/sbin/crash_serializer", args...)
out, err := cmd.Output()
if err != nil {
s.Fatal("Failed to run crash_serializer: ", err)
}
// Else, output will be the serialized protos.
var protos []*tls.FetchCrashesResponse
for len(out) != 0 {
size := binary.BigEndian.Uint64(out[0:8])
out = out[8:]
next := &tls.FetchCrashesResponse{}
if err := proto.Unmarshal(out[:size], next); err != nil {
s.Fatal("Failed to unmarshal proto: ", err)
}
out = out[size:]
protos = append(protos, next)
}
expectedLen := 2
if addCore {
expectedLen += coreChunks
}
if len(protos) != expectedLen {
s.Fatalf("Unexpected number of protos. Want %d, got %d ", expectedLen, len(protos))
}
crashID := protos[0].CrashId
switch x := protos[0].Data.(type) {
case *tls.FetchCrashesResponse_Crash:
if exp.Version != x.Crash.Ver {
s.Errorf("Unexpected version. Want %s, got %s", exp.Version, x.Crash.Ver)
}
if exp.Product != x.Crash.Prod {
s.Errorf("Unexpected product. Want %s, got %s", exp.Product, x.Crash.Prod)
}
if !verifyMetaField(ctx, "board", exp.Board, x.Crash) {
s.Error("Failed to verify board")
}
if !verifyMetaField(ctx, "hwclass", exp.HWClass, x.Crash) {
s.Error("Failed to verify hwclass")
}
if exp.Executable != x.Crash.ExecName {
s.Errorf("Unexpected exec name. Want %s, got %s", exp.Executable, x.Crash.ExecName)
}
default:
s.Errorf("Unexpected oneof type for protos[0]: %T", x)
}
if protos[1].CrashId != crashID {
s.Errorf("CrashID unexpectedly changed from %d to %d", crashID, protos[1].CrashId)
}
switch x := protos[1].Data.(type) {
case *tls.FetchCrashesResponse_Blob:
basePayload := filepath.Base(exp.PayloadPath)
if basePayload != x.Blob.Filename {
s.Errorf("Unexpected filename. Want %s, got %s", basePayload, x.Blob.Filename)
}
contents, err := ioutil.ReadFile(exp.PayloadPath)
if err != nil {
s.Fatal("Failed to read payload file: ", err)
}
if !bytes.Equal(contents, x.Blob.Blob) {
s.Error("Unexpected blob. Saving actual and expected")
if err := crash.MoveFilesToOut(ctx, s.OutDir(), exp.PayloadPath); err != nil {
s.Error("Failed to save expected payload: ", err)
}
if err := ioutil.WriteFile(filepath.Join(s.OutDir(), basePayload+".actual"), x.Blob.Blob, 0664); err != nil {
s.Error("Failed to save actual payload: ", err)
}
}
default:
s.Errorf("Unexpected oneof type for protos[1]: %T", x)
}
if addCore {
var core []byte
for i := 2; i < expectedLen; i++ {
if protos[i].CrashId != crashID {
s.Errorf("index %d: CrashID unexpectedly changed from %d to %d", i, crashID, protos[i].CrashId)
}
switch x := protos[i].Data.(type) {
case *tls.FetchCrashesResponse_Core:
core = append(core, x.Core...)
default:
s.Errorf("Unexpected oneof type for protos[%d]: %T", i, x)
}
}
contents, err := ioutil.ReadFile(coreName)
if err != nil {
s.Fatal("Failed to read core file: ", err)
}
if !bytes.Equal(contents, core) {
s.Error("Unexpected core contents. Saving actual and expected")
if err := crash.MoveFilesToOut(ctx, s.OutDir(), coreName); err != nil {
s.Error("Failed to save expected core: ", err)
}
if err := ioutil.WriteFile(filepath.Join(s.OutDir(), filepath.Base(coreName+".actual")), core, 0664); err != nil {
s.Error("Failed to save actual core: ", err)
}
}
}
}