Add tparallel task

This commit is contained in:
Fedor Korotkiy 2020-03-18 18:35:54 +03:00
parent f96155ec96
commit 43cf8dcd0e
3 changed files with 242 additions and 0 deletions

23
tparallel/README.md Normal file
View file

@ -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()`. Такой тест должен продолжить исполнение, после того как функция родительского теста
завершилась.

18
tparallel/tparallel.go Normal file
View file

@ -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")
}

201
tparallel/tparallel_test.go Normal file
View file

@ -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)
})
},
})
}