Skip to content

Commit

Permalink
implement abstraction over clock source & add tests
Browse files Browse the repository at this point in the history
  • Loading branch information
mfontani committed Jan 7, 2021
1 parent d7bec99 commit 0a07c7f
Show file tree
Hide file tree
Showing 5 changed files with 290 additions and 5 deletions.
69 changes: 69 additions & 0 deletions clock.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package main

import "time"

// Time is a custom interface to ensure we can test timestamp()
type Time interface {
Now() time.Time
Sub(time.Time) time.Duration
}

type realClock struct{}

func (realClock) Now() time.Time { return time.Now() }
func (c realClock) Sub(t time.Time) time.Duration { return c.Sub(t) }

type stuckClock struct {
sec int64
nsec int64
}

func (c stuckClock) Now() time.Time {
return time.Unix(c.sec, c.nsec)
}

func (c stuckClock) Sub(t time.Time) time.Duration {
when := time.Unix(c.sec, c.nsec)
return when.Sub(time.Unix(c.sec, c.nsec))
}

// StuckClock creates a new "stuck" clock, which starts at the given sec, nsec
// and always returns itself for any Sub() call
func StuckClock(sec, nsec int64) Time {
return stuckClock{sec: sec, nsec: nsec}
}

type monotonicClock struct {
sec int64
nsec int64
secIncrease int64
nsecIncrease int64
}

func (c *monotonicClock) Now() time.Time {
then := time.Unix(c.sec, c.nsec)
c.sec += c.secIncrease
c.nsec += c.nsecIncrease
for c.nsec > 1_000_000_000 {
c.sec++
c.nsec -= 1_000_000_000
}
return then
}

func (c monotonicClock) Sub(t time.Time) time.Duration {
when := time.Unix(c.sec, c.nsec)
return when.Sub(t)
}

// To ensure MonotonicClock can do its thing, create an instance of it which
// gets changed by all calls to Now() and friends.
var theMonotonicClock monotonicClock

// MonotonicClock creates a new "stuck" clock, which starts at the given sec,
// nsec and whenever Now() is called, it returns the last time incremented by
// the given delta seconds and nsec.
func MonotonicClock(sec, nsec, secIncrease, nsecIncrease int64) Time {
theMonotonicClock = monotonicClock{sec: sec, nsec: nsec, secIncrease: secIncrease, nsecIncrease: nsecIncrease}
return &theMonotonicClock
}
30 changes: 30 additions & 0 deletions clock_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package main

import (
"testing"
)

func TestMonotonicClock(t *testing.T) {
c := MonotonicClock(0, 0, 0, 5_000_000)
now1 := c.Now()
now2 := c.Now()
if now1.Unix() == now2.Unix() && now1.UnixNano() == now2.UnixNano() {
t.Fail()
t.Logf("now1==now2 = %v & %v & %v.%v", now1, now2, now1.Unix(), now1.UnixNano())
return
}
if now2.Unix() != now2.Unix() {
t.Fail()
t.Logf("now1.Unix()==now2.Unix() = %v & %v", now1, now2)
return
}
if now2.UnixNano() != (now1.UnixNano() + 5_000_000) {
t.Fail()
t.Logf("now2.UnixNano()!=now1.UnixNano()+5_000_000 = now1:%v (%d.%d) & now2:%v (%d.%d) => %d!=%d",
now1, now1.Unix(), now1.UnixNano(),
now2, now2.Unix(), now2.UnixNano(),
now2.UnixNano(), now1.UnixNano()+5_000_000,
)
return
}
}
3 changes: 2 additions & 1 deletion main.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,6 @@ var WantsColors = false

func main() {
dealWithArgs()
timestamp(os.Stdin, os.Stdout, WantsColors)
stdOut := os.Stdout
timestamp(realClock{}, os.Stdin, stdOut, WantsColors)
}
9 changes: 5 additions & 4 deletions timestamper.go → timestamp.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,13 @@ func niceDuration(d time.Duration) string {
)
}

func timestamp(r io.Reader, w io.Writer, wantsColors bool) {
func timestamp(clock Time, r io.Reader, w io.Writer, wantsColors bool) {
scanner := bufio.NewScanner(r)
startTime := time.Now()
lastLine := time.Now()
startTime := clock.Now()
// lastLine := clock.Now()
lastLine := startTime
for scanner.Scan() {
nowTime := time.Now()
nowTime := clock.Now()
sinceStart := nowTime.Sub(startTime)
sinceLastLine := nowTime.Sub(lastLine)
color := ""
Expand Down
184 changes: 184 additions & 0 deletions timestamp_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
package main

import (
"bytes"
"os"
"testing"
)

var fakeEpoch int64 = 909090909

// perl -lE'say scalar gmtime 909090909'
// Thu Oct 22 21:15:09 1998

func TestTimestampSingleLine(t *testing.T) {
if os.Getenv("TZ") != "UTC" {
t.Fail()
t.Logf("Run tests with TZ=UTC!")
return
}
c := StuckClock(fakeEpoch, 0)
in := bytes.NewBufferString("foo")
var outB bytes.Buffer
timestamp(c, in, &outB, false)
wantS := "1998-10-22 21:15:09.000000 00:00:00.000000 00:00:00.000000 foo\n"
if outB.String() != wantS {
t.Fail()
t.Logf("GOT bytes: %v", outB)
t.Logf("WANT string:\n%v", wantS)
t.Logf("GOT string:\n%v", outB.String())
}
}

func TestTimestampTwoLines(t *testing.T) {
if os.Getenv("TZ") != "UTC" {
t.Fail()
t.Logf("Run tests with TZ=UTC!")
return
}
c := StuckClock(fakeEpoch, 0)
in := bytes.NewBufferString("foo\nbar")
var outB bytes.Buffer
timestamp(c, in, &outB, false)
wantS := "1998-10-22 21:15:09.000000 00:00:00.000000 00:00:00.000000 foo\n1998-10-22 21:15:09.000000 00:00:00.000000 00:00:00.000000 bar\n"
if outB.String() != wantS {
t.Fail()
t.Logf("GOT bytes: %v", outB)
t.Logf("WANT string:\n%v", wantS)
t.Logf("GOT string:\n%v", outB.String())
}
}

func TestTimestampTwoLinesColored(t *testing.T) {
if os.Getenv("TZ") != "UTC" {
t.Fail()
t.Logf("Run tests with TZ=UTC!")
return
}
c := StuckClock(fakeEpoch, 0)
in := bytes.NewBufferString("foo\nbar")
var outB bytes.Buffer
timestamp(c, in, &outB, true)
wantS := "1998-10-22 21:15:09.000000 00:00:00.000000 00:00:00.000000 foo\n1998-10-22 21:15:09.000000 00:00:00.000000 00:00:00.000000 bar\n"
if outB.String() != wantS {
t.Fail()
t.Logf("GOT bytes: %v", outB)
t.Logf("WANT string:\n%v", wantS)
t.Logf("GOT string:\n%v", outB.String())
}
}

func TestTimestampTwoLinesHalfSecond(t *testing.T) {
if os.Getenv("TZ") != "UTC" {
t.Fail()
t.Logf("Run tests with TZ=UTC!")
return
}
c := MonotonicClock(fakeEpoch, 0, 0, 500_000_000)
in := bytes.NewBufferString("foo\nbar")
var outB bytes.Buffer
timestamp(c, in, &outB, false)
wantS := "1998-10-22 21:15:09.500000 00:00:00.500000 00:00:00.500000 foo\n1998-10-22 21:15:10.000000 00:00:01.000000 00:00:00.500000 bar\n"
if outB.String() != wantS {
t.Fail()
t.Logf("GOT bytes: %v", outB)
t.Logf("WANT string:\n%v", wantS)
t.Logf("GOT string:\n%v", outB.String())
}
}

func TestTimestampTwoLinesHalfSecondColored(t *testing.T) {
if os.Getenv("TZ") != "UTC" {
t.Fail()
t.Logf("Run tests with TZ=UTC!")
return
}
c := MonotonicClock(fakeEpoch, 0, 0, 500_000_000)
in := bytes.NewBufferString("foo\nbar")
var outB bytes.Buffer
timestamp(c, in, &outB, true)
wantS := "1998-10-22 21:15:09.500000 00:00:00.500000 00:00:00.500000 foo\n1998-10-22 21:15:10.000000 00:00:01.000000 00:00:00.500000 bar\n"
if outB.String() != wantS {
t.Fail()
t.Logf("GOT bytes: %v", outB)
t.Logf("WANT string:\n%v", wantS)
t.Logf("GOT string:\n%v", outB.String())
}
}

func TestTimestampTwoLinesOneHalfSecond(t *testing.T) {
if os.Getenv("TZ") != "UTC" {
t.Fail()
t.Logf("Run tests with TZ=UTC!")
return
}
c := MonotonicClock(fakeEpoch, 0, 1, 500_000_000)
in := bytes.NewBufferString("foo\nbar")
var outB bytes.Buffer
timestamp(c, in, &outB, false)
wantS := "1998-10-22 21:15:10.500000 00:00:01.500000 00:00:01.500000 foo\n1998-10-22 21:15:12.000000 00:00:03.000000 00:00:01.500000 bar\n"
if outB.String() != wantS {
t.Fail()
t.Logf("GOT bytes: %v", outB)
t.Logf("WANT string:\n%v", wantS)
t.Logf("GOT string:\n%v", outB.String())
}
}

func TestTimestampTwoLinesOneHalfSecondColored(t *testing.T) {
if os.Getenv("TZ") != "UTC" {
t.Fail()
t.Logf("Run tests with TZ=UTC!")
return
}
c := MonotonicClock(fakeEpoch, 0, 1, 500_000_000)
in := bytes.NewBufferString("foo\nbar")
var outB bytes.Buffer
timestamp(c, in, &outB, true)
wantS := "1998-10-22 21:15:10.500000 00:00:01.500000 \033[33m00:00:01.500000\033[0m foo\n1998-10-22 21:15:12.000000 00:00:03.000000 \033[33m00:00:01.500000\033[0m bar\n"
if outB.String() != wantS {
t.Fail()
t.Logf("WANT bytes: %v", []byte(wantS))
t.Logf("GOT bytes: %v", outB)
t.Logf("WANT string:\n%v", wantS)
t.Logf("GOT string:\n%v", outB.String())
}
}

func TestTimestampTwoLinesNinetySeconds(t *testing.T) {
if os.Getenv("TZ") != "UTC" {
t.Fail()
t.Logf("Run tests with TZ=UTC!")
return
}
c := MonotonicClock(fakeEpoch, 0, 90, 0)
in := bytes.NewBufferString("foo\nbar")
var outB bytes.Buffer
timestamp(c, in, &outB, false)
wantS := "1998-10-22 21:16:39.000000 00:01:30.000000 00:01:30.000000 foo\n1998-10-22 21:18:09.000000 00:03:00.000000 00:01:30.000000 bar\n"
if outB.String() != wantS {
t.Fail()
t.Logf("GOT bytes: %v", outB)
t.Logf("WANT string:\n%v", wantS)
t.Logf("GOT string:\n%v", outB.String())
}
}

func TestTimestampTwoLinesNinetySecondsColored(t *testing.T) {
if os.Getenv("TZ") != "UTC" {
t.Fail()
t.Logf("Run tests with TZ=UTC!")
return
}
c := MonotonicClock(fakeEpoch, 0, 90, 0)
in := bytes.NewBufferString("foo\nbar")
var outB bytes.Buffer
timestamp(c, in, &outB, true)
wantS := "1998-10-22 21:16:39.000000 00:01:30.000000 \033[31m00:01:30.000000\033[0m foo\n1998-10-22 21:18:09.000000 00:03:00.000000 \033[31m00:01:30.000000\033[0m bar\n"
if outB.String() != wantS {
t.Fail()
t.Logf("GOT bytes: %v", outB)
t.Logf("WANT string:\n%v", wantS)
t.Logf("GOT string:\n%v", outB.String())
}
}

0 comments on commit 0a07c7f

Please sign in to comment.