diff --git a/Makefile b/Makefile index f9ce466..8bc405b 100644 --- a/Makefile +++ b/Makefile @@ -33,7 +33,7 @@ test: deps ## Run tests bench: deps ## Run benchmarks # ^$ filters out every unit test, so only benchmarks will run - go test -run "^$" -benchmem -bench . ./... + go test -run '^$$' -benchmem -bench . ./... coverage-report: ## Generate coverage report from previous test run go tool cover -html "$(COVERAGE_OUT)" -o "$(COVERAGE_HTML)" diff --git a/internal/pkg/worklog/entry_test.go b/internal/pkg/worklog/entry_test.go index 21d9d9a..51621fa 100644 --- a/internal/pkg/worklog/entry_test.go +++ b/internal/pkg/worklog/entry_test.go @@ -11,7 +11,7 @@ import ( "github.com/stretchr/testify/assert" ) -func getTestEntry() worklog.Entry { +func getCompleteTestEntry() worklog.Entry { start := time.Date(2021, 10, 2, 5, 0, 0, 0, time.UTC) end := start.Add(time.Hour * 2) @@ -36,6 +36,12 @@ func getTestEntry() worklog.Entry { } } +func getIncompleteTestEntry() worklog.Entry { + entry := getCompleteTestEntry() + entry.Task = worklog.IDNameField{} + return entry +} + func TestIDNameFieldIsComplete(t *testing.T) { var field worklog.IDNameField @@ -54,39 +60,39 @@ func TestIDNameFieldIsComplete(t *testing.T) { } func TestEntryKey(t *testing.T) { - entry := getTestEntry() + entry := getCompleteTestEntry() assert.Equal(t, "Internal projects:TASK-0123:Write worklog transfer CLI tool:2021-10-02", entry.Key()) } func TestEntryIsComplete(t *testing.T) { - entry := getTestEntry() + entry := getCompleteTestEntry() assert.True(t, entry.IsComplete()) } func TestEntryIsCompleteIncomplete(t *testing.T) { var entry worklog.Entry - entry = getTestEntry() + entry = getCompleteTestEntry() entry.Client = worklog.IDNameField{} assert.False(t, entry.IsComplete()) - entry = getTestEntry() + entry = getCompleteTestEntry() entry.Project = worklog.IDNameField{} assert.False(t, entry.IsComplete()) - entry = getTestEntry() + entry = getCompleteTestEntry() entry.Task = worklog.IDNameField{} assert.False(t, entry.IsComplete()) - entry = getTestEntry() + entry = getCompleteTestEntry() entry.Summary = "" assert.False(t, entry.IsComplete()) - entry = getTestEntry() + entry = getCompleteTestEntry() entry.Start = time.Time{} assert.False(t, entry.IsComplete()) - entry = getTestEntry() + entry = getCompleteTestEntry() entry.BillableDuration = 0 entry.UnbillableDuration = 0 assert.False(t, entry.IsComplete()) @@ -95,7 +101,7 @@ func TestEntryIsCompleteIncomplete(t *testing.T) { func TestEntry_SplitDuration(t *testing.T) { var splitBillable time.Duration var splitUnbillable time.Duration - entry := getTestEntry() + entry := getCompleteTestEntry() splitBillable, splitUnbillable = entry.SplitDuration(1) assert.Equal(t, entry.BillableDuration, splitBillable) @@ -108,7 +114,7 @@ func TestEntry_SplitDuration(t *testing.T) { } func TestEntry_SplitByTag(t *testing.T) { - entry := getTestEntry() + entry := getCompleteTestEntry() regex, err := regexp.Compile(`^TASK-\d+$`) require.Nil(t, err) diff --git a/internal/pkg/worklog/worklog.go b/internal/pkg/worklog/worklog.go index 3ba6897..8549d04 100644 --- a/internal/pkg/worklog/worklog.go +++ b/internal/pkg/worklog/worklog.go @@ -1,17 +1,32 @@ package worklog -// groupEntries ensures to group similar entries, identified by their key. -// If the keys are matching for two entries, those will be merged and their duration will be summed up, notes will be -// concatenated. -func groupEntries(entries []Entry) []Entry { - entryGroup := map[string]Entry{} +// Worklog is the collection of multiple Entries. +type Worklog struct { + completeEntries []Entry + incompleteEntries []Entry +} + +// CompleteEntries returns those entries which necessary fields were filled. +func (w *Worklog) CompleteEntries() []Entry { + return w.completeEntries +} + +// IncompleteEntries is the opposite of CompleteEntries. +func (w *Worklog) IncompleteEntries() []Entry { + return w.incompleteEntries +} + +// NewWorklog creates a worklog from the given set of entries and merges them. +func NewWorklog(entries []Entry) Worklog { + worklog := Worklog{} + mergedEntries := map[string]Entry{} for _, entry := range entries { key := entry.Key() - storedEntry, isStored := entryGroup[key] + storedEntry, isStored := mergedEntries[key] if !isStored { - entryGroup[key] = entry + mergedEntries[key] = entry continue } @@ -27,48 +42,16 @@ func groupEntries(entries []Entry) []Entry { storedEntry.Notes = storedEntry.Notes + noteSeparator + entry.Notes } - entryGroup[key] = storedEntry + mergedEntries[key] = storedEntry } - groupedEntries := make([]Entry, 0, len(entryGroup)) - for _, entry := range entryGroup { - groupedEntries = append(groupedEntries, entry) - } - - return groupedEntries -} - -// Worklog is the collection of multiple Entries. -type Worklog struct { - entries []Entry -} - -// entryGroup returns those entries that are matching the completeness criteria. -func (w *Worklog) entryGroup(isComplete bool) []Entry { - var entries []Entry - - for _, entry := range w.entries { - if entry.IsComplete() == isComplete { - entries = append(entries, entry) + for _, entry := range mergedEntries { + if entry.IsComplete() { + worklog.completeEntries = append(worklog.completeEntries, entry) + } else { + worklog.incompleteEntries = append(worklog.incompleteEntries, entry) } } - return entries -} - -// CompleteEntries returns those entries which necessary fields were filled. -func (w *Worklog) CompleteEntries() []Entry { - return w.entryGroup(true) -} - -// IncompleteEntries is the opposite of CompleteEntries. -func (w *Worklog) IncompleteEntries() []Entry { - return w.entryGroup(false) -} - -// NewWorklog creates a worklog from the given set of entries and groups them. -func NewWorklog(entries []Entry) Worklog { - return Worklog{ - entries: groupEntries(entries), - } + return worklog } diff --git a/internal/pkg/worklog/worklog_test.go b/internal/pkg/worklog/worklog_test.go index 94138ce..0c6c745 100644 --- a/internal/pkg/worklog/worklog_test.go +++ b/internal/pkg/worklog/worklog_test.go @@ -8,54 +8,111 @@ import ( "github.com/stretchr/testify/assert" ) -var worklogBenchResult worklog.Worklog +var newWorklogBenchResult worklog.Worklog +var completeEntriesBenchResult []worklog.Entry +var incompleteEntriesBenchResult []worklog.Entry -func benchmarkNewWorklog(b *testing.B, entryCount int) { +func benchNewWorklog(b *testing.B, entryCount int) { b.StopTimer() var entries []worklog.Entry for i := 0; i != entryCount; i++ { - entry := getTestEntry() + entry := getCompleteTestEntry() entry.Start.Add(time.Hour * time.Duration(i)) entries = append(entries, entry) } b.StartTimer() - var result worklog.Worklog for n := 0; n != b.N; n++ { - result = worklog.NewWorklog(entries) + // always store the result to a package level variable + // so the compiler cannot eliminate the Benchmark itself. + newWorklogBenchResult = worklog.NewWorklog(entries) } +} + +func benchmarkCompleteEntries(b *testing.B, entryCount int) { + b.StopTimer() + + var entries []worklog.Entry + + for i := 0; i != entryCount; i++ { + entry := getCompleteTestEntry() + entry.Start.Add(time.Hour * time.Duration(i)) + entries = append(entries, entry) + } + + wl := worklog.NewWorklog(entries) - // always store the result to a package level variable - // so the compiler cannot eliminate the Benchmark itself. - worklogBenchResult = result + b.StartTimer() + + for n := 0; n != b.N; n++ { + // always store the result to a package level variable + // so the compiler cannot eliminate the Benchmark itself. + completeEntriesBenchResult = wl.CompleteEntries() + } } -func BenchmarkNewWorklog_5(b *testing.B) { - benchmarkNewWorklog(b, 5) +func benchmarkIncompleteEntries(b *testing.B, entryCount int) { + b.StopTimer() + + var entries []worklog.Entry + + for i := 0; i != entryCount; i++ { + entry := getIncompleteTestEntry() + entry.Start.Add(time.Hour * time.Duration(i)) + entries = append(entries, entry) + } + + wl := worklog.NewWorklog(entries) + + b.StartTimer() + + for n := 0; n != b.N; n++ { + // always store the result to a package level variable + // so the compiler cannot eliminate the Benchmark itself. + incompleteEntriesBenchResult = wl.IncompleteEntries() + } } func BenchmarkNewWorklog_10(b *testing.B) { - benchmarkNewWorklog(b, 10) + benchNewWorklog(b, 10) + _ = newWorklogBenchResult // Use the result to eliminate linter issues +} + +func BenchmarkNewWorklog_1000(b *testing.B) { + benchNewWorklog(b, 1000) + _ = newWorklogBenchResult // Use the result to eliminate linter issues +} + +func BenchmarkCompleteEntries_10(b *testing.B) { + benchmarkCompleteEntries(b, 10) + _ = completeEntriesBenchResult // Use the result to eliminate linter issues +} + +func BenchmarkCompleteEntries_1000(b *testing.B) { + benchmarkCompleteEntries(b, 1000) + _ = completeEntriesBenchResult // Use the result to eliminate linter issues } -func BenchmarkNewWorklog_50(b *testing.B) { - benchmarkNewWorklog(b, 50) +func BenchmarkIncompleteEntries_10(b *testing.B) { + benchmarkIncompleteEntries(b, 10) + _ = incompleteEntriesBenchResult // Use the result to eliminate linter issues } -func BenchmarkNewWorklog_100(b *testing.B) { - benchmarkNewWorklog(b, 100) +func BenchmarkIncompleteEntries_1000(b *testing.B) { + benchmarkIncompleteEntries(b, 1000) + _ = incompleteEntriesBenchResult // Use the result to eliminate linter issues } func TestWorklogCompleteEntries(t *testing.T) { - completeEntry := getTestEntry() + completeEntry := getCompleteTestEntry() - otherCompleteEntry := getTestEntry() + otherCompleteEntry := getCompleteTestEntry() otherCompleteEntry.Notes = "Really" - incompleteEntry := getTestEntry() + incompleteEntry := getCompleteTestEntry() incompleteEntry.Task = worklog.IDNameField{} wl := worklog.NewWorklog([]worklog.Entry{ @@ -70,12 +127,12 @@ func TestWorklogCompleteEntries(t *testing.T) { } func TestWorklogIncompleteEntries(t *testing.T) { - completeEntry := getTestEntry() + completeEntry := getCompleteTestEntry() - incompleteEntry := getTestEntry() + incompleteEntry := getCompleteTestEntry() incompleteEntry.Task = worklog.IDNameField{} - otherIncompleteEntry := getTestEntry() + otherIncompleteEntry := getCompleteTestEntry() otherIncompleteEntry.Task = worklog.IDNameField{} otherIncompleteEntry.Notes = "Well, not that easy"