1// Copyright 2019 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package runtime_test
6
7import (
8	"bytes"
9	"encoding/binary"
10	"errors"
11	"internal/testenv"
12	"os/exec"
13	"reflect"
14	"runtime"
15	"testing"
16	"time"
17)
18
19func TestFakeTime(t *testing.T) {
20	if runtime.GOOS == "windows" {
21		t.Skip("faketime not supported on windows")
22	}
23
24	// Faketime is advanced in checkdead. External linking brings in cgo,
25	// causing checkdead not working.
26	testenv.MustInternalLink(t, false)
27
28	t.Parallel()
29
30	exe, err := buildTestProg(t, "testfaketime", "-tags=faketime")
31	if err != nil {
32		t.Fatal(err)
33	}
34
35	var stdout, stderr bytes.Buffer
36	cmd := exec.Command(exe)
37	cmd.Stdout = &stdout
38	cmd.Stderr = &stderr
39
40	err = testenv.CleanCmdEnv(cmd).Run()
41	if err != nil {
42		t.Fatalf("exit status: %v\n%s", err, stderr.String())
43	}
44
45	t.Logf("raw stdout: %q", stdout.String())
46	t.Logf("raw stderr: %q", stderr.String())
47
48	f1, err1 := parseFakeTime(stdout.Bytes())
49	if err1 != nil {
50		t.Fatal(err1)
51	}
52	f2, err2 := parseFakeTime(stderr.Bytes())
53	if err2 != nil {
54		t.Fatal(err2)
55	}
56
57	const time0 = 1257894000000000000
58	got := [][]fakeTimeFrame{f1, f2}
59	var want = [][]fakeTimeFrame{{
60		{time0 + 1, "line 2\n"},
61		{time0 + 1, "line 3\n"},
62		{time0 + 1e9, "line 5\n"},
63		{time0 + 1e9, "2009-11-10T23:00:01Z"},
64	}, {
65		{time0, "line 1\n"},
66		{time0 + 2, "line 4\n"},
67	}}
68	if !reflect.DeepEqual(want, got) {
69		t.Fatalf("want %v, got %v", want, got)
70	}
71}
72
73type fakeTimeFrame struct {
74	time uint64
75	data string
76}
77
78func parseFakeTime(x []byte) ([]fakeTimeFrame, error) {
79	var frames []fakeTimeFrame
80	for len(x) != 0 {
81		if len(x) < 4+8+4 {
82			return nil, errors.New("truncated header")
83		}
84		const magic = "\x00\x00PB"
85		if string(x[:len(magic)]) != magic {
86			return nil, errors.New("bad magic")
87		}
88		x = x[len(magic):]
89		time := binary.BigEndian.Uint64(x)
90		x = x[8:]
91		dlen := binary.BigEndian.Uint32(x)
92		x = x[4:]
93		data := string(x[:dlen])
94		x = x[dlen:]
95		frames = append(frames, fakeTimeFrame{time, data})
96	}
97	return frames, nil
98}
99
100func TestTimeTimerType(t *testing.T) {
101	// runtime.timeTimer (exported for testing as TimeTimer)
102	// must have time.Timer and time.Ticker as a prefix
103	// (meaning those two must have the same layout).
104	runtimeTimeTimer := reflect.TypeOf(runtime.TimeTimer{})
105
106	check := func(name string, typ reflect.Type) {
107		n1 := runtimeTimeTimer.NumField()
108		n2 := typ.NumField()
109		if n1 != n2+1 {
110			t.Errorf("runtime.TimeTimer has %d fields, want %d (%s has %d fields)", n1, n2+1, name, n2)
111			return
112		}
113		for i := 0; i < n2; i++ {
114			f1 := runtimeTimeTimer.Field(i)
115			f2 := typ.Field(i)
116			t1 := f1.Type
117			t2 := f2.Type
118			if t1 != t2 && !(t1.Kind() == reflect.UnsafePointer && t2.Kind() == reflect.Chan) {
119				t.Errorf("runtime.Timer field %s %v incompatible with %s field %s %v", f1.Name, t1, name, f2.Name, t2)
120			}
121			if f1.Offset != f2.Offset {
122				t.Errorf("runtime.Timer field %s offset %d incompatible with %s field %s offset %d", f1.Name, f1.Offset, name, f2.Name, f2.Offset)
123			}
124		}
125	}
126
127	check("time.Timer", reflect.TypeOf(time.Timer{}))
128	check("time.Ticker", reflect.TypeOf(time.Ticker{}))
129}
130