Перейти к содержимому

Fuzz-тестирование в Go

Зачем знать: Fuzz-тесты ищут падения там, где разработчик не подумал — невалидные данные, пограничные случаи, странные кодировки. С Go 1.18 fuzzing встроен в testing, а в 2026 он уже выловил сотни багов в stdlib (net/http, encoding/json, archive/tar). На middle 1 от вас ждут понимания: где fuzz применим (парсеры, валидаторы, decoder’ы), как читать crash-файлы, как комбинировать fuzz с race-детектором и интегрировать в CI. Без этих знаний сложно отвечать за надёжность critical-сервиса.

  1. Базовая концепция
  2. Под капотом / Best practices
  3. Gotchas
  4. Производительность
  5. Вопросы на собеседовании
  6. Practice
  7. Источники

Fuzz testing (fuzzing) — автоматическая подача случайных или направленных-случайных данных в функцию, чтобы найти input, на котором она падает (panic), зацикливается или ведёт себя некорректно (например, нарушает инварианты).

В отличие от unit-теста:

АспектUnitFuzz
ЦельПроверить заданный кейсНайти кейс, на котором падает
ВходыЗахардкоденыСгенерированы (random + corpus-guided)
ДлительностьМиллисекундыСекунды-часы (на CI обычно 30-300 секунд per fuzz)
Что считается failРазличие got vs wantpanic, error, нарушение инварианта
Когда писатьВсегдаПарсеры, валидаторы, decoder, security-критика
  • До Go 1.18: внешний инструмент dvyukov/go-fuzz. Требовал отдельной компиляции, отдельной command-line утилиты.
  • Go 1.18 (март 2022): testing.F встроен в stdlib. Аналог: компиляция с инструментацией, генерация corpus, crash-сохранение — всё через go test.
  • 2023-2025: fuzz нашёл реальные баги в encoding/json, net/http, archive/tar, regexp и др.
  • 2026: рекомендован для CI на security-чувствительных сервисах.
package myparser
import "testing"
// FuzzParse тестирует Parse на random входах.
func FuzzParse(f *testing.F) {
// Корпус — стартовые seed'ы.
f.Add([]byte(`{"a":1}`))
f.Add([]byte(`{}`))
f.Add([]byte(`null`))
f.Fuzz(func(t *testing.T, data []byte) {
// Эта функция вызывается с разными data: сначала seed, потом random.
v, err := Parse(data)
if err != nil {
// err допустим, паника — нет.
return
}
// Property: парсинг идемпотентен.
re, err := json.Marshal(v)
if err != nil {
t.Fatalf("re-marshal: %v", err)
}
v2, err := Parse(re)
if err != nil {
t.Fatalf("re-parse: %v", err)
}
if !reflect.DeepEqual(v, v2) {
t.Fatalf("not idempotent")
}
})
}

Запуск:

Окно терминала
go test -fuzz=FuzzParse -fuzztime=30s

Если фуззер находит вход, на котором тест падает, он сохраняет файл в testdata/fuzz/FuzzParse/<hash> — после этого этот вход становится частью обычных тестов: go test ./... пройдёт по нему как regression.


func FuzzXxx(f *testing.F) {
// 1) Seed corpus: примеры валидных/интересных входов.
f.Add(seed1)
f.Add(seed2)
// 2) Можно подгрузить корпус из файла программно.
// Корпус из testdata/fuzz/FuzzXxx подхватывается автоматически.
// 3) Сама функция тестирования.
f.Fuzz(func(t *testing.T, args ...) {
// args — те же типы, что и f.Add(...).
// 1 аргумент — 1 параметр fuzz.
})
}

Допустимые типы аргументов в f.Fuzz/f.Add:

  • string, []byte
  • int, int8-int64, uint, uint8-uint64, rune, byte
  • float32, float64
  • bool

Сложные типы (struct, slice кроме []byte) не поддерживаются — нужно сериализовать в строку/байты и разобрать внутри.

Два источника seed corpus:

  1. f.Add(...) — внутри fuzz-теста, в коде. Эти seeds выполняются всегда (даже в обычном go test).
  2. testdata/fuzz/FuzzName/... — файлы со специальным форматом. Загружаются автоматически.

Формат файла corpus:

go test fuzz v1
[]byte("hello\xff")
int(42)

Каждая строка — тип(значение). Чтобы вручную добавить корпус — обычно проще писать f.Add(), а файлы появляются сами от падений или gotip save.

Окно терминала
# Только regression (без fuzzing) — стандартный тест:
go test ./parser
# Запуск fuzzing на 30 секунд:
go test -fuzz=FuzzParse -fuzztime=30s ./parser
# С race detector:
go test -fuzz=FuzzParse -fuzztime=30s -race ./parser
# Параллельно (по умолчанию = GOMAXPROCS):
go test -fuzz=FuzzParse -parallel=8 -fuzztime=1m
# Воспроизведение crash:
go test -run=FuzzParse/abcdef12345 ./parser

-fuzztime может принимать 30s, 5m, 2h или count’у запусков: 100x, 1000000x.

При нахождении вход, на котором тест падает, fuzzer записывает входы (по одному файлу на параметр функции) в:

testdata/fuzz/FuzzParse/<sha256-hash>

Эти файлы должны коммититься в репозиторий. После коммита:

  • go test ./... без -fuzz пройдёт по ним как regression.
  • Если кто-то починит баг, файл всё равно остаётся — гарантия, что не вернётся.
Окно терминала
go test -run=FuzzParse/abc123def ./parser

Здесь abc123def — имя файла из testdata/fuzz/FuzzParse/. Это уникальный кейс, который раньше падал. Полезно для отладки в IDE: ставишь breakpoint, запускаешь конкретный.

go test -fuzz использует coverage-guided fuzzing (как AFL/libFuzzer):

  1. Компилирует код с дополнительной инструментацией для отслеживания, какие branch’и (basic blocks) выполняются.
  2. Запускает функцию с входом.
  3. Если новый вход покрыл новую ветку — добавляет его в очередь интересных.
  4. Мутирует интересные входы (byte flips, arithmetic, splice) — генерирует новые.

Это эффективнее random: миллионы random попыток дадут мало coverage, а guided — за минуты доходит до глубоких веток.

Под капотом: Go компилирует с -buildmode=... инструментацией (внутри cmd/go), хранит coverage map в shared memory между runner и worker.

Fuzzer спавнит N workers (по умолчанию GOMAXPROCS). Каждый worker:

  • Хранит свой PRNG.
  • Получает задание от координатора.
  • Запускает функцию, отчитывается о покрытии.
  • Если crash — координатор минимизирует input (shrinks) и сохраняет.

Сравнение двух реализаций одной функции:

func FuzzDecodeCompare(f *testing.F) {
f.Add([]byte(`hello`))
f.Fuzz(func(t *testing.T, data []byte) {
gotA, errA := decoderA(data)
gotB, errB := decoderB(data)
if (errA == nil) != (errB == nil) {
t.Fatalf("err mismatch: A=%v B=%v on %q", errA, errB, data)
}
if errA == nil && !bytes.Equal(gotA, gotB) {
t.Fatalf("diff: A=%x B=%x on %q", gotA, gotB, data)
}
})
}

Полезно для:

  • Своя реализация vs reference (stdlib).
  • Старая vs новая (refactoring).
  • Compatible decoders (JSON variant A vs B).

Похожи, но разные:

АспектProperty-based (rapid, gopter)Fuzz (testing.F)
ГенераторСтруктурный (Int, String, struct)Byte-уровень + coverage-guided
ЦельПроверить свойство на N кейсахНайти crash на любом входе
Типы аргументовЛюбые (struct, slice)Только примитивы
Минимизация (shrink)ДаДа (но проще)
Сохранение seed’овНет (или вручную)Автоматически (testdata)
Coverage-guidedНетДа
Long-runningОбычно секундыМинуты-часы

В реальных проектах используют оба: property для бизнес-инвариантов (на структурах), fuzz для парсеров и I/O.

Google OSS-Fuzz (https://google.github.io/oss-fuzz/) — бесплатное непрерывное fuzzing для open-source проектов. Подключают:

  1. Регистрируешь проект в OSS-Fuzz.
  2. Описываешь project.yaml, Dockerfile, build.sh.
  3. Указываешь fuzz targets (FuzzXxx функции).
  4. Google запускает их 24/7 на их инфраструктуре, репортит баги.

Многие популярные Go проекты (containerd, etcd, prometheus, kubernetes utils) — в OSS-Fuzz.

Хорошие кандидаты:

  • Парсеры: JSON, YAML, TOML, XML, HTTP-headers, URL, S-expressions, configs.
  • Decoder’ы: protobuf, MsgPack, base64, ASN.1.
  • Валидаторы: regex, email, URL, IBAN.
  • State machines с явным входом (FSM по байтам).
  • Криптография (с осторожностью).

Плохие кандидаты:

  • Pure compute без ветвлений (например, fmt.Sprintf("%d", x)).
  • Deterministic logic без зависимости от данных (сортировка интов — будет coverage один и тот же).
  • Сетевые операции — flaky.
  • Что требует БД/файлы — slow + non-deterministic.

Примеры реальных багов, найденных fuzz в Go:

  • encoding/json: panic на конкретных формах nested arrays.
  • archive/tar: out-of-bounds read.
  • net/http: CRLF injection в HeaderValues.
  • regexp: stack overflow на регэкспах с глубокой рекурсией.
  • golang.org/x/text: панические Unicode normalisations.

Google и Cloudflare регулярно публикуют отчёты о найденных багах через fuzz.

func FuzzMarshalRoundTrip(f *testing.F) {
f.Add([]byte(`{"a":1}`))
f.Fuzz(func(t *testing.T, data []byte) {
var v any
if err := json.Unmarshal(data, &v); err != nil {
return
}
b, err := json.Marshal(v)
if err != nil {
t.Fatalf("marshal: %v", err)
}
var v2 any
if err := json.Unmarshal(b, &v2); err != nil {
t.Fatalf("re-unmarshal: %v err=%v", b, err)
}
if !reflect.DeepEqual(v, v2) {
t.Fatalf("roundtrip mismatch")
}
})
}
func FuzzNoPanic(f *testing.F) {
f.Add([]byte("hello"))
f.Fuzz(func(t *testing.T, data []byte) {
defer func() {
if r := recover(); r != nil {
t.Fatalf("panic: %v", r)
}
}()
_ = MyParse(data)
})
}

(на самом деле, в testing.F panic в fuzz-функции и так считается failure — recover не обязателен, но добавляет message.)

func FuzzSorted(f *testing.F) {
f.Add(int64(42), int64(7), int64(15))
f.Fuzz(func(t *testing.T, a, b, c int64) {
sorted := mySort([]int64{a, b, c})
for i := 1; i < len(sorted); i++ {
if sorted[i-1] > sorted[i] {
t.Fatalf("not sorted: %v", sorted)
}
}
})
}

f.Add(MyStruct{...}) // КОМПИЛЯЦИЯ НЕ ПРОЙДЁТ

Нельзя struct, []T (кроме []byte). Сериализуйте: f.Add([]byte(buf)), внутри f.Fuzz разворачивайте.

f.Add(int(42))
f.Fuzz(func(t *testing.T, x int64) {}) // panic: типы int и int64 не совпадают

testing.F строго проверяет: типы f.Add(...) должны совпадать точно с типами в f.Fuzz (после первого аргумента *testing.T).

Если ваша функция работает 100ms, за минуту будет всего 600 итераций. Эффективный fuzz требует функций в микросекунды.

Оптимизируйте: убрать I/O, БД, аллокации, лишние логи.

Если функция зацикливается, fuzz зависнет. Используйте timeout внутри:

ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
defer cancel()
_ = MyFunc(ctx, data)

Или ограничьте размер data:

if len(data) > 1<<20 {
return // skip too large inputs
}
var counter int
func FuzzBad(f *testing.F) {
f.Fuzz(func(t *testing.T, data []byte) {
counter++ // RACE между workers!
// ...
})
}

Workers запускаются параллельно. Global state — RACE. Используйте локальные переменные.

Файлы из testdata/fuzz/FuzzName/ должны коммититься (regression suite). Не добавляйте в .gitignore.

Но! testdata/fuzz/FuzzName/cache/ — кэш фаззера (большой), его игнорируем. По умолчанию хранится в $GOCACHE/fuzz/.

go test -fuzz=... -cover работает, но coverage от fuzz runs обычно невысокий (фокус на crash, не на coverage отчёт). Для покрытия — отдельная сессия go test -cover.

//go:build fuzz
package mypkg
func FuzzX(f *testing.F) { ... }

Можно изолировать fuzz-тесты от обычных, если они тяжёлые. Запускать: go test -tags=fuzz -fuzz=FuzzX.

Окно терминала
go test -fuzz=FuzzX -run=TestY # ошибка

Можно запускать только один fuzz target за раз. Параметр -run фильтрует regression-тесты в том же запуске, но -fuzz=FuzzX запускает именно FuzzX.

В f.Fuzz(func(t *testing.T, data []byte) {...}) внутри t — это *testing.T, не *testing.F. Используйте t.Errorf, t.Fatalf, t.Helper как обычно.

*testing.F доступен только в FuzzXxx для f.Add, f.Fuzz, f.Skip (и наследует методы T).

t.Parallel() внутри f.Fuzz не имеет смысла — fuzz runner и так запускает параллельно. Не пишите t.Parallel() внутри.

Когда fuzzer находит crash, он минимизирует вход: пытается убрать байты/уменьшить числа, чтобы найти minimal failing input. Это автоматически, но если ваша функция долгая, минимизация займёт время.

Если функция использует goroutine, time, randomness — crash может быть невоспроизводим:

go test -run=FuzzX/abc123 # PASS
go test -run=FuzzX/abc123 # FAIL

Лечение: детерминизм. Передавайте rand.Source, clock, не запускайте goroutine.

Глубоко зарытые баги (нужна конкретная последовательность из 100 байт) fuzzer может не найти за час, но найти за день. Не отчаивайтесь.

Идиомы:

.github/workflows/fuzz.yml
- run: go test -fuzz=FuzzParse -fuzztime=10m ./parser
# потом артефакт testdata/fuzz сохраняем

Но не на каждый PR — это долго. Обычно: nightly или manual.

Go хранит:

  • $GOCACHE/test/ — кэш go test результатов.
  • $GOCACHE/fuzz/<pkg>/<FuzzName>/ — наработки фаззера (coverage map, interesting inputs).

При запуске fuzz он использует свой кэш — даже если вы запускаете повторно, не начинает с нуля. Чтобы reset:

Окно терминала
go clean -fuzzcache

Полезно: между major переписками функции.

Fuzz работает только для пакетов внутри текущего модуля. Нельзя go test -fuzz=... для зависимости из vendor.

f.Add строго проверяет типы:

f.Add(int64(42))
f.Fuzz(func(t *testing.T, x int) {}) // PANIC: int64 != int

Используйте те же типы. int и int64 — разные!

Можно. Но запускается только один за раз:

func FuzzA(f *testing.F) { ... }
func FuzzB(f *testing.F) { ... }
Окно терминала
go test -fuzz=FuzzA ./pkg
# Если -fuzz=Fuzz — runner вернёт ошибку (несколько targets матчатся).

Fuzz должен быть детерминистичен на одном входе: дважды запустили с тем же data → одинаковый результат. Иначе minimisation и reproduction не работают.

Если функция использует random, time, goroutines — это проблема. Решения:

  • Передавать rand.Source извне.
  • Использовать time.Time параметром, не time.Now().
  • Не запускать goroutines в fuzz-функции.

Цель — функция в микросекунды. Замеряйте:

func BenchmarkParse(b *testing.B) {
data := []byte(`{"a":1}`)
for i := 0; i < b.N; i++ {
_, _ = Parse(data)
}
}

Если BenchmarkParse показывает 50µs/op, за минуту fuzz сделает ~1.2M вызовов на 1 core, ~10M на 8 cores. Это нормально.

Каждая аллокация — ~50ns. Если функция аллоцирует 100 объектов на вызов — 5µs на GC. Используйте sync.Pool, переиспользуйте буферы (но осторожно — fuzz mutates input!).

f.Fuzz(func(t *testing.T, data []byte) {
// НЕ делайте: cached := buf.Get() и кешируйте data в Pool — между вызовами!
})

-parallel=N или -fuzz сама использует GOMAXPROCS. На 16-ядерном — будет 16 workers. Каждый кушает ~1 core.

Worker’ы держат corpus в RAM. Большой corpus = много памяти. Очищайте старые с go clean -fuzzcache.

go test -race -fuzz=FuzzX -fuzztime=1m. Замедление в 2-10x. Но race-баги без race-детектора не найти. Рекомендация: запускать race fuzz отдельно от обычного, например, nightly.

Если ваша функция течёт памятью, после миллионов итераций OOM. Тестируйте на benchmark с b.N=1000000.

Race detector — единственный встроенный sanitizer в Go (нет ASan/MSan как в C++). Но Go и так memory-safe — большинство ошибок это panic/nil deref, а не buffer overflow. Race + fuzz покрывают большинство проблем.

С coverage-guided мутацией fuzzing на порядки эффективнее random:

  • Random: 10⁶ попыток дают <1% coverage сложного парсера.
  • Coverage-guided: 10⁵ попыток достигают 80%+.

OSS-Fuzz распределяет fuzzing на десятки CPU. Локально — параллельно на одном хосте. Distributed multi-host для Go в stdlib нет — нужны внешние решения (ClusterFuzz).

Fuzzer выводит:

fuzz: elapsed: 0s, gathering baseline coverage: 0/156 completed
fuzz: elapsed: 3s, gathering baseline coverage: 156/156 completed, now fuzzing with 8 workers
fuzz: elapsed: 6s, execs: 12345 (4115/sec), new interesting: 12 (total: 168)
  • execs/sec — основная метрика. Целевые значения:
    • 100k/sec — отличная функция (мелкая, без I/O).

    • 10k-100k/sec — норма для парсеров.
    • <1k/sec — медленно, оптимизируйте функцию.
  • new interesting — растёт первые минуты, потом плато. Если плато низкое — coverage слабый, фаззер не нашёл новых веток.
Окно терминала
go test -fuzz=FuzzX -fuzztime=30s -memprofile=mem.prof
go tool pprof mem.prof

Если функция аллоцирует на каждой итерации — pprof покажет hot path. Оптимизируйте через sync.Pool (но не мутируйте input).

Окно терминала
go test -fuzz=FuzzX -fuzztime=30s -cpuprofile=cpu.prof
go tool pprof cpu.prof

Видно, где функция тратит время. Часто узкое место — JSON/regex/reflection.

При -parallel=N каждый worker — отдельный Go process. Память умножается на N. На 16-ядерной машине с тяжёлой setup-функцией memory может вылететь.

Лечение: уменьшить -parallel, либо облегчить setup.

Можно вызывать другую функцию из fuzz:

func FuzzAll(f *testing.F) {
f.Add([]byte("..."))
f.Fuzz(func(t *testing.T, data []byte) {
result1 := ParseA(data)
result2 := ParseB(result1)
result3 := ParseC(result2)
// конец цепочки
})
}

Эффективно: один прогон тестит всю pipeline. Но shrinking сложнее — фаззер не знает, в каком этапе крах.


1. Что такое fuzz testing? Автоматическая подача случайных или направленных-случайных данных в функцию для поиска панических ситуаций, ошибок, нарушений инвариантов.

2. С какой версии Go fuzz встроен в stdlib? Go 1.18 (март 2022). До этого был dvyukov/go-fuzz.

3. Что такое f.Add? Добавляет seed-вход в corpus. Запускается всегда, плюс используется как стартовая точка для мутаций fuzzer’ом.

4. Какие типы можно использовать в f.Add / f.Fuzz? Примитивные: string, []byte, целые (int, int32, …), uint*, float32/64, bool, rune, byte. Не: struct, slice кроме []byte.

5. Что делает -fuzztime? Длительность fuzzing-сессии. -fuzztime=30s — 30 секунд, -fuzztime=100x — 100 итераций.

6. Где сохраняются падающие inputs? В testdata/fuzz/FuzzName/<hash>. Эти файлы коммитятся в репозиторий как regression suite.

7. Как воспроизвести crash? go test -run=FuzzName/HASH ./pkg — запустит конкретный crash как regular test.

8. Что такое coverage-guided fuzzing? Фаззер отслеживает, какие code branches выполняются. Если новый input покрыл новую ветку — добавляется в очередь и мутируется. Эффективнее random.

9. В чём разница fuzz и property-based? Property-based генерирует структурно (struct, slice), не coverage-guided. Fuzz — на байтовом уровне, coverage-guided, сохраняет corpus автоматически.

10. Что фуззить, а что нет? Парсеры, декодеры, валидаторы, security-критичный код — да. Pure compute, простую логику без ветвлений — нет.

11. Можно ли совмещать fuzz и -race? Да: go test -fuzz=FuzzX -race. Замедляет 2-10x, но ловит data races.

12. Что такое minimisation (shrinking)? После crash фаззер пытается уменьшить input до минимально воспроизводимого. Помогает разработчику быстрее понять баг.

13. Что такое differential fuzzing? Подаём один и тот же вход двум реализациям (своя vs reference). Если результаты разные — баг.

14. Что такое OSS-Fuzz? Сервис Google, бесплатно фуззит open-source проекты на их инфраструктуре. Поддерживает Go с 2020.

15. Параллельность fuzz? По умолчанию GOMAXPROCS worker’ов. Можно -parallel=N.

16. Что произойдёт, если fuzz-функция бесконечно зацикливается? Worker зависнет. Используйте timeout внутри (context) или ограничивайте размер input.

17. Как fuzz tests интегрируются в CI? Обычно nightly или manual: -fuzztime=5m дополнительно к regular tests. Crash-файлы автоматически становятся regression-тестами.

18. Какие реальные баги нашёл fuzz в stdlib? В encoding/json, net/http, archive/tar, regexp — десятки issues с 2020 года.

19. Когда не делать fuzz? Pure compute без ветвлений (сортировка интов), deterministic logic (HMAC от фиксированного ключа без branch’ей по входу).

20. Что лучше: fuzz или unit-тесты? Это разные инструменты. Unit — для конкретных кейсов. Fuzz — для поиска неизвестных кейсов. В большом проекте — оба.

21. Можно ли использовать struct в f.Fuzz? Нет, только примитивные типы. Сериализуйте struct в []byte (например, через gob или binary), разворачивайте внутри.

22. Что произойдёт, если fuzz-функция panic’ует? Считается failure, вход сохраняется в testdata/fuzz/FuzzName/<hash>. Минимизируется.

23. Что такое seed corpus? Стартовые входы для фаззера: через f.Add или файлы в testdata/fuzz/. Без них фаззер начинает с пустых байтов.

24. Как фаззер мутирует входы? Byte flips (XOR bit), arithmetic changes (+1, -1), splice (combine two inputs), insert/delete bytes, dictionary substitution.

25. Почему fuzz coverage-guided эффективнее random? Сохраняет интересные входы (которые открыли новую ветку) и мутирует их. Random не имеет памяти — миллионы попыток на одном и том же.

26. Как организовать fuzz target для security audit? Декомпозируйте: парсер → валидатор → бизнес-логика. Каждый этап — отдельный fuzz target. Так minimisation легче.

27. Как минимизация работает? Когда найден crash, фаззер пробует уменьшить вход (отрезать байты, уменьшить числа), пока тест ещё падает. Итог — минимальный воспроизводимый вход.

28. Можно ли использовать t.Parallel внутри f.Fuzz? Технически да, но бессмысленно: fuzz runner и так параллелит инвокации. Не используйте.

29. Что выводит go test -fuzz=FuzzX -v? Каждые ~1 секунду — elapsed, execs (вызовов), execs/sec, new interesting (новых интересных входов).

30. Что делать, если fuzz нашёл баг, который сложно воспроизвести? Проверьте детерминизм: time, goroutines, random source. Если зависит — uplift в передаваемый параметр.


Напишите ParseCSV([]byte) ([][]string, error). Fuzz должен находить inputs, на которых ваш парсер panic’ует (включая \r\n, обрезанные кавычки, NUL-байты).

Напишите fuzz FuzzJSONRoundTrip для собственного encoder/decoder. Property: decode(encode(x)) == x.

Реализуйте свой base64Decode и сравните с encoding/base64.StdEncoding.DecodeString. Найдите расхождения через fuzz.

Напишите ValidateEmail(s string) bool. Fuzz должен находить:

  • email с \n внутри (не валидный, но ваш код пропускает).
  • слишком длинные локальные части (>64).

Реализуйте FSM (например, простой парсер JSON-like). Fuzz должен находить последовательности байтов, на которых FSM попадает в недопустимое состояние.

Запустите fuzz на 1 минуту с -fuzztime=1m, потом go test -cover — сравните покрытие до и после fuzz сессии (через изучение testdata/fuzz/).

Реализуйте Sort([]int) []int. Fuzz проверяет: длина не изменилась, всё отсортировано, multiset исходного и результата совпадают.

Создайте функцию с известным багом (например, divide(a, b) = a/b без проверки b==0). Запустите fuzz, найдите файл в testdata/fuzz/, исправьте баг, убедитесь что regression проходит.


  1. Go fuzzing docs: https://go.dev/doc/fuzz/ — туториал и reference.
  2. Go Blog “Fuzzing is Beta Ready” (2021) и “Fuzzing in Go” (1.18): https://go.dev/blog/fuzz-beta, https://go.dev/blog/go1.18.
  3. testing.F docs: https://pkg.go.dev/testing#F.
  4. OSS-Fuzz Go docs: https://google.github.io/oss-fuzz/getting-started/new-project-guide/go-lang/.
  5. Article “Go Fuzzing in Practice” (Cloudflare blog, 2022).
  6. Уязвимости найденные fuzz: https://github.com/golang/go/issues?q=label%3AFuzzing.
  7. rapid (property-based): https://pkg.go.dev/pgregory.net/rapid.
  8. Talk “Native Go Fuzzing” Katie Hockman, GopherCon 2022.
  9. gophers slack #fuzzing channel — обсуждения практик.