diff --git a/tparallel/README.md b/tparallel/README.md new file mode 100644 index 0000000..6a030fc --- /dev/null +++ b/tparallel/README.md @@ -0,0 +1,23 @@ +# tparallel + +Реализуйте api, дублирующее поведение `t.Parallel()`. + +Тест - это функция с сигнатурой `func(*T)`. Вам нужно реализовать функцию `Run(topTests []func(*T))`, +запускающую множество top-level тестов. + +Каждый тест получает аргументом уникальный объект `*T`. + +В начате все тесты выполняются последовательно. Если тест вызывает `t.Parallel()`, то он становится +паралельным и блокируется на этом вызове. После этого, запускается следующий последовательный тест. + +Когда все последовательные тесты завершились, все паралельные тесты разблокируются и продолжают своё +исполнения. + +Функция `Run` выходит, когда завершились все тесты. + +Тест может запускать под-тесты вызывая `t.Run`. Тест считается завершённым, когда завершился он сам и +завершились все его подтесты. + +Под-тест начинает своё выполнение последовательно, блокирую вызов Run. Подтест может стать параллельным, +вызвав `t.Parallel()`. Такой тест должен продолжить исполнение, после того как функция родительского теста +завершилась. diff --git a/tparallel/tparallel.go b/tparallel/tparallel.go new file mode 100644 index 0000000..50550a1 --- /dev/null +++ b/tparallel/tparallel.go @@ -0,0 +1,18 @@ +// +build !solution + +package tparallel + +type T struct { +} + +func (t *T) Parallel() { + panic("implement me") +} + +func (t *T) Run(subtest func(t *T)) { + panic("implement me") +} + +func Run(topTests []func(t *T)) { + panic("implement me") +} diff --git a/tparallel/tparallel_test.go b/tparallel/tparallel_test.go new file mode 100644 index 0000000..208a22b --- /dev/null +++ b/tparallel/tparallel_test.go @@ -0,0 +1,201 @@ +package tparallel + +import ( + "sync" + "testing" +) + +type ConcurrencyChecker struct { + t *testing.T + + mu sync.Mutex + nextStage int + waitCount int + barrier chan struct{} +} + +func (c *ConcurrencyChecker) Sequential(stage int) { + c.t.Helper() + c.t.Logf("Sequential(%d)", stage) + + c.mu.Lock() + defer c.mu.Unlock() + + if stage != c.nextStage { + c.t.Errorf("testing method is executed out of sequence: expected=%d, got=%d", c.nextStage, stage) + return + } + + c.nextStage++ +} + +func (c *ConcurrencyChecker) Parallel(stage, count int) { + c.t.Helper() + c.t.Logf("Parallel(%d, %d)", stage, count) + + var barrier chan struct{} + + func() { + c.mu.Lock() + defer c.mu.Unlock() + + if stage != c.nextStage { + c.t.Errorf("testing method is executed out of sequence: expected=%d, got=%d", c.nextStage, stage) + return + } + + if c.waitCount == 0 { + c.barrier = make(chan struct{}) + } + barrier = c.barrier + + c.waitCount++ + + if c.waitCount == count { + c.waitCount = 0 + c.nextStage++ + close(c.barrier) + } + }() + + <-barrier +} + +func (c *ConcurrencyChecker) Finish(total int) { + if total != c.nextStage { + c.t.Errorf("wrong number of stages executed: expected=%d, got=%d", total, c.nextStage) + } +} + +func TestSequentialExecution(t *testing.T) { + check := &ConcurrencyChecker{t: t} + defer check.Finish(3) + + Run([]func(*T){ + func(t *T) { + check.Sequential(0) + }, + func(t *T) { + check.Sequential(1) + }, + func(t *T) { + check.Sequential(2) + }, + }) +} + +func TestParallelExecution(t *testing.T) { + check := &ConcurrencyChecker{t: t} + defer check.Finish(4) + + Run([]func(*T){ + func(t *T) { + check.Sequential(0) + t.Parallel() + check.Parallel(3, 3) + }, + func(t *T) { + check.Sequential(1) + t.Parallel() + check.Parallel(3, 3) + }, + func(t *T) { + check.Sequential(2) + t.Parallel() + check.Parallel(3, 3) + }, + }) +} + +func TestSequentialSubTests(t *testing.T) { + check := &ConcurrencyChecker{t: t} + defer check.Finish(5) + + Run([]func(*T){ + func(t *T) { + check.Sequential(0) + + t.Run(func(t *T) { + check.Sequential(1) + + t.Run(func(t *T) { + check.Sequential(2) + }) + }) + + t.Run(func(t *T) { + check.Sequential(3) + }) + }, + func(t *T) { + check.Sequential(4) + }, + }) +} + +func TestParallelGroup(t *testing.T) { + check := &ConcurrencyChecker{t: t} + defer check.Finish(17) + + Run([]func(*T){ + func(t *T) { + check.Sequential(0) + }, + func(t *T) { + check.Sequential(1) + + t.Run(func(t *T) { + check.Sequential(2) + + for i := 0; i < 10; i++ { + t.Run(func(t *T) { + check.Sequential(3 + i) + + t.Parallel() + + check.Parallel(14, 10) + }) + } + + check.Sequential(13) + }) + + check.Sequential(15) + }, + func(t *T) { + check.Sequential(16) + }, + }) +} + +func TestTwoParallelSequences(t *testing.T) { + check := &ConcurrencyChecker{t: t} + defer check.Finish(4) + + Run([]func(*T){ + func(t *T) { + check.Sequential(0) + t.Parallel() + + t.Run(func(t *T) { + check.Parallel(2, 2) + }) + + t.Run(func(t *T) { + check.Parallel(3, 2) + }) + }, + func(t *T) { + check.Sequential(1) + t.Parallel() + + t.Run(func(t *T) { + check.Parallel(2, 2) + }) + + t.Run(func(t *T) { + check.Parallel(3, 2) + }) + }, + }) +}