Merge branch 'fileleak' into 'master'

Add fileleak task

See merge request slon/shad-go-private!45
This commit is contained in:
Fedor Korotkiy 2021-03-12 17:56:18 +00:00
commit d62c1f2865
3 changed files with 117 additions and 0 deletions

26
fileleak/README.md Normal file
View file

@ -0,0 +1,26 @@
# fileleak
Реализуйте библиотеку для поиска утечек файлов в тестах.
Библиотека содержит единственную функцию - `VerifyNone`. Пользователь должен вызвать эту функцию в начале своего теста.
Библиотека смотрит на все открытые файлы процесса в начале и в конце теста. Если в конце теста будут находятся открытые файлы,
которые не были открыты на момент старта теста, то библиотека фейлит весь тест.
Эта задача будет корректно работать только на `linux`. Если вы пользуетесь другой операционной системой,
вам придётся отлаживаться через CI.
Чтобы узнать открытые файлы процесса, нужно прочитать директорию `/proc/self/fd`.
Эта директория содержит символические ссылки. Именем ссылки является номер файлового дескриптора, а значением
ссылки является какое-то текстовое описание открытого файла. Значение ссылки можно прочитать, используя `os.Readlink`.
```bash
prime@bee ~/C/shad-go> ls -lah /proc/self/fd
total 0
dr-x------ 2 prime prime 0 мар 12 19:44 ./
dr-xr-xr-x 9 prime prime 0 мар 12 19:44 ../
lrwx------ 1 prime prime 64 мар 12 19:44 0 -> /dev/pts/7
lrwx------ 1 prime prime 64 мар 12 19:44 1 -> /dev/pts/7
lrwx------ 1 prime prime 64 мар 12 19:44 2 -> /dev/pts/7
lr-x------ 1 prime prime 64 мар 12 19:44 3 -> /proc/871308/fd/
```

12
fileleak/fileleak.go Normal file
View file

@ -0,0 +1,12 @@
// +build !solution
package fileleak
type testingT interface {
Errorf(msg string, args ...interface{})
Cleanup(func())
}
func VerifyNone(t testingT) {
panic("implement me")
}

79
fileleak/fileleak_test.go Normal file
View file

@ -0,0 +1,79 @@
package fileleak_test
import (
"bytes"
"fmt"
"io/ioutil"
"os"
"testing"
"github.com/stretchr/testify/require"
"gitlab.com/slon/shad-go/fileleak"
)
type fakeT struct {
failed bool
buffer bytes.Buffer
cleanup []func()
}
func (f *fakeT) Errorf(msg string, args ...interface{}) {
f.failed = true
fmt.Fprintf(&f.buffer, msg, args...)
}
func (f *fakeT) Cleanup(cb func()) {
f.cleanup = append([]func(){cb}, f.cleanup...)
}
func checkLeak(t *testing.T, shouldDetect bool, leaker func()) {
var fake fakeT
fileleak.VerifyNone(&fake)
leaker()
for _, cb := range fake.cleanup {
cb()
}
switch {
case shouldDetect && !fake.failed:
t.Errorf("fileleak didn't detect a leak")
case !shouldDetect && fake.failed:
t.Errorf("fileleak detected a leak when there is none:\n%s", fake.buffer.String())
}
}
func TestFileLeak_OpenFile(t *testing.T) {
var f *os.File
checkLeak(t, true, func() {
var err error
f, err = os.Open("/proc/self/exe")
require.NoError(t, err)
})
if f != nil {
_ = f.Close()
}
}
func TestFileLeak_AlwaysOpenFile(t *testing.T) {
f, err := os.Open("/proc/self/exe")
require.NoError(t, err)
defer f.Close()
checkLeak(t, false, func() {})
}
func TestFileLeak_ReopenFile(t *testing.T) {
f, err := os.Open("/proc/self/exe")
require.NoError(t, err)
defer f.Close()
checkLeak(t, true, func() {
_ = f.Close()
ff, err := ioutil.TempFile("", "")
require.NoError(t, err)
f = ff
})
}