Руководство: Начало работы с фаззингом
В этом руководстве представлены основы фаззинга в Go. С помощью фаззинга случайные данные запускаются против ваших тестов с целью поиска уязвимостей или входных данных, вызывающих сбои. Некоторые примеры уязвимостей, которые можно обнаружить с помощью фаззинга: SQL-инъекции, переполнение буфера, атаки типа "отказ в обслуживании" и межсайтовый скриптинг.
В этом руководстве вы напишете фазз-тест для простой функции, запустите команду go и отладите и исправите проблемы в коде.
Для справки по терминологии в этом руководстве см. Глоссарий Go Fuzzing.
Вы пройдете следующие разделы:
- Создайте папку для вашего кода.
- Добавьте код для тестирования.
- Добавьте модульный тест.
- Добавьте фазз-тест.
- Исправьте две ошибки.
- Ознакомьтесь с дополнительными ресурсами.
Примечание: Для других руководств см. Руководства.
Примечание: Фаззинг в Go в настоящее время поддерживает подмножество встроенных типов, перечисленных в документации Go Fuzzing, с добавлением поддержки других встроенных типов в будущем.
Предварительные требования
- Установленный Go 1.18 или более поздней версии. Инструкции по установке см. Установка Go.
- Инструмент для редактирования кода. Подойдет любой текстовый редактор.
- Командная строка. Go хорошо работает в любой терминальной среде на Linux и Mac, а также в PowerShell или cmd в Windows.
- Среда, поддерживающая фаззинг. Фаззинг в Go с инструментированием покрытия доступен только на архитектурах AMD64 и ARM64.
Создайте папку для вашего кода
Для начала создайте папку для кода, который вы будете писать.
-
Откройте командную строку и перейдите в домашнюю директорию.
На Linux или Mac:
<code>$ cd </code>
В Windows:
<code>C:\> cd %HOMEPATH% </code>
В остальной части руководства будет отображаться символ $ как приглашение. Команды, которые вы будете использовать, работают и в Windows.
-
Из командной строки создайте директорию для вашего кода под названием fuzz.
<code>$ mkdir fuzz $ cd fuzz </code>
-
Создайте модуль для хранения вашего кода.
Выполните команду
go mod init, указав путь к вашему новому модулю.<code>$ go mod init example/fuzz go: creating new go.mod: module example/fuzz </code>
Примечание: Для production-кода вы должны указать путь к модулю, который будет более специфичным для ваших нужд. За дополнительной информацией обращайтесь к разделу Управление зависимостями.
Далее вы добавите простой код для обращения строки, который позже будет использован в fuzz-тестировании.
Добавление кода для тестирования
На этом шаге вы добавите функцию для обращения строки.
Написание кода
-
С помощью текстового редактора создайте файл под названием main.go в директории fuzz.
-
В начале файла main.go вставьте следующее объявление пакета.
<code>package main </code>
Автономная программа (в отличие от библиотеки) всегда находится в пакете
main. -
Под объявлением пакета вставьте следующее объявление функции.
<code>func Reverse(s string) string { b := []byte(s) for i, j := 0, len(b)-1; i < len(b)/2; i, j = i+1, j-1 { b[i], b[j] = b[j], b[i] } return string(b) } </code>Эта функция принимает
string, проходит по нему поbyteи возвращает обращённую строку.Примечание: Этот код основан на функции
stringutil.Reverseвнутри golang.org/x/example. -
В начале файла main.go, под объявлением пакета, вставьте следующую функцию
main, чтобы инициализировать строку, обращать её, выводить результат и повторять.<code>func main() { input := "The quick brown fox jumped over the lazy dog" rev := Reverse(input) doubleRev := Reverse(rev) fmt.Printf("original: %q\n", input) fmt.Printf("reversed: %q\n", rev) fmt.Printf("reversed again: %q\n", doubleRev) } </code>Эта функция выполнит несколько операций
Reverse, а затем выведет результат в командной строке. Это может быть полезно для просмотра работы кода и отладки. -
Функция
mainиспользует пакет fmt, поэтому его нужно импортировать.Первые строки кода должны выглядеть так:
<code>package main import "fmt" </code>
Запуск кода
Из командной строки в директории, содержащей main.go, запустите код.
<code>$ go run . original: "The quick brown fox jumped over the lazy dog" reversed: "god yzal eht revo depmuj xof nworb kciuq ehT" reversed again: "The quick brown fox jumped over the lazy dog" </code>
Вы можете увидеть исходную строку, результат обращения её, а затем результат обращения результата, который эквивалентен исходной строке.
Теперь, когда код запущен, пришло время его протестировать.
Добавление модульного теста
На этом шаге вы напишете базовый модульный тест для функции Reverse.
Написание кода
-
С помощью текстового редактора создайте файл под названием reverse_test.go в директории fuzz.
-
Вставьте следующий код в файл reverse_test.go.
<code>package main import ( "testing" ) func TestReverse(t *testing.T) { testcases := []struct { in, want string }{ {"Hello, world", "dlrow ,olleH"}, {" ", " "}, {"!12345", "54321!"}, } for _, tc := range testcases { rev := Reverse(tc.in) if rev != tc.want { t.Errorf("Reverse: %q, want %q", rev, tc.want) } } } </code>Этот простой тест проверяет, что указанные входные строки будут корректно обращены.
Запуск кода
Запустите модульный тест с помощью команды go test
<code>$ go test PASS ok example/fuzz 0.013s </code>
Далее вы преобразуете модульный тест в fuzz-тест.
Добавление fuzz-теста
Юнит-тесты имеют ограничения, а именно, что каждое входное значение должно быть добавлено в тест разработчиком. Одним из преимуществ фаззинга является то, что он генерирует входные данные для вашего кода и может выявить граничные случаи, которые не были учтены в ваших исходных тестовых случаях.
В этом разделе вы преобразуете юнит-тест в фазз-тест, чтобы иметь возможность генерировать больше входных данных с меньшими усилиями!
Обратите внимание, что юнит-тесты, бенчмарки и фазз-тесты можно хранить в одном файле с расширением *_test.go, но в данном примере вы преобразуете юнит-тест в фазз-тест.
Напишите код
В вашем текстовом редакторе замените юнит-тест в файле reverse_test.go следующим фазз-тестом.
<code>func FuzzReverse(f *testing.F) {
testcases := []string{"Hello, world", " ", "!12345"}
for _, tc := range testcases {
f.Add(tc) // Используйте f.Add для предоставления начального набора входных данных
}
f.Fuzz(func(t *testing.T, orig string) {
rev := Reverse(orig)
doubleRev := Reverse(rev)
if orig != doubleRev {
t.Errorf("Before: %q, after: %q", orig, doubleRev)
}
if utf8.ValidString(orig) && !utf8.ValidString(rev) {
t.Errorf("Reverse produced invalid UTF-8 string %q", rev)
}
})
}
</code>
У фаззинга тоже есть некоторые ограничения. В юнит-тесте вы могли предсказать ожидаемый результат работы функции
Reverse и проверить, соответствует ли фактический результат ожиданиям.
Например, в тестовом случае Reverse("Hello, world") юнит-тест указывает возвращаемое значение как
"dlrow ,olleH".
При фаззинге предсказать ожидаемый результат невозможно, поскольку вы не контролируете входные данные.
Однако, существуют некоторые свойства функции Reverse, которые можно проверить в фазз-тесте. Два
свойства, проверяемые в данном фазз-тесте:
- Двойное обращение строки сохраняет исходное значение
- Обращённая строка сохраняет корректность UTF-8.
Обратите внимание на различия в синтаксисе между юнит-тестом и фазз-тестом:
- Функция начинается с FuzzXxx вместо TestXxx, и принимает
*testing.Fвместо*testing.T - Вместо ожидаемого выполнения
t.Runвы видитеf.Fuzz, который принимает целевую функцию фаззинга, параметры которой —*testing.Tи типы, подлежащие фаззингу. Входные данные из вашего юнит-теста предоставляются как начальный набор входных данных с использованиемf.Add.
Убедитесь, что импортирован новый пакет unicode/utf8.
<code>package main import ( "testing" "unicode/utf8" ) </code>
После того как юнит-тест был преобразован в фазз-тест, пришло время снова запустить тест.
Запустите код
-
Запустите фазз-тест без фаззинга, чтобы убедиться, что начальные входные данные проходят проверку.
<code>$ go test PASS ok example/fuzz 0.013s </code>
Также можно выполнить
go test -run=FuzzReverse, если в этом файле есть другие тесты, и вы хотите запустить только фазз-тест. -
Запустите
FuzzReverseс фаззингом, чтобы проверить, вызовет ли какая-либо случайно сгенерированная строка ошибку. Это выполняется с помощьюgo testс новым флагом-fuzz, установленным в значениеFuzz. Скопируйте команду ниже.<code>$ go test -fuzz=Fuzz </code>
Другой полезный флаг — это
-fuzztime, который ограничивает время, необходимое для запуска тестирования с помощью fuzz-тестирования. Например, указание-fuzztime 10sв тесте ниже означает, что, при отсутствии преждевременных ошибок, тест завершится по умолчанию через 10 секунд. См. эту раздел документации cmd/go, чтобы ознакомиться с другими флагами тестирования.Теперь выполните только что скопированную команду.
<code>$ go test -fuzz=Fuzz fuzz: elapsed: 0s, gathering baseline coverage: 0/3 completed fuzz: elapsed: 0s, gathering baseline coverage: 3/3 completed, now fuzzing with 8 workers fuzz: minimizing 38-byte failing input file... --- FAIL: FuzzReverse (0.01s) --- FAIL: FuzzReverse (0.00s) reverse_test.go:20: Reverse produced invalid UTF-8 string "\x9c\xdd" Failing input written to testdata/fuzz/FuzzReverse/af69258a12129d6cbba438df5d5f25ba0ec050461c116f777e77ea7c9a0d217a To re-run: go test -run=FuzzReverse/af69258a12129d6cbba438df5d5f25ba0ec050461c116f777e77ea7c9a0d217a FAIL exit status 1 FAIL example/fuzz 0.030s </code>
Произошла ошибка во время fuzz-тестирования, и входные данные, вызвавшие проблему, записаны в файл seed-корпуса, который будет выполнен при следующем вызове
go test, даже без флага-fuzz. Чтобы просмотреть входные данные, вызвавшие сбой, откройте файл корпуса, записанный в директорию testdata/fuzz/FuzzReverse, в текстовом редакторе. Ваш файл seed-корпуса может содержать другую строку, но формат будет тот же.<code>go test fuzz v1 string("泃") </code>Первая строка файла корпуса указывает версию кодировки. Каждая последующая строка представляет значение каждого типа, составляющего запись корпуса. Поскольку целевой объект fuzz-теста принимает только один входной параметр, после версии находится только одно значение.
-
Выполните
go testснова без флага-fuzz; новый элемент seed-корпуса, вызвавший сбой, будет использован:<code>$ go test --- FAIL: FuzzReverse (0.00s) --- FAIL: FuzzReverse/af69258a12129d6cbba438df5d5f25ba0ec050461c116f777e77ea7c9a0d217a (0.00s) reverse_test.go:20: Reverse produced invalid string FAIL exit status 1 FAIL example/fuzz 0.016s </code>
Поскольку наш тест завершился неудачей, пришло время отладки.
Исправьте ошибку с недопустимой строкой
В этом разделе вы отладите сбой и исправите ошибку.
Не стесняйтесь потратить немного времени на размышления и попытки самостоятельно исправить проблему перед тем, как продолжить.
Диагностика ошибки
Существует несколько способов отладки этой ошибки. Если вы используете VS Code в качестве текстового редактора, вы можете настроить отладчик для исследования.
В этом руководстве мы будем выводить полезную информацию для отладки в терминал.
Сначала ознакомьтесь с документацией по
utf8.ValidString.
<code>ValidString reports whether s consists entirely of valid UTF-8-encoded runes. </code>
Текущая функция Reverse переворачивает строку побайтово, и именно здесь заключается наша проблема.
Чтобы сохранить UTF-8-кодированные символы исходной строки, необходимо переворачивать строку по символам
(runes).
Чтобы понять, почему входные данные (в данном случае китайский символ 泃) вызывают ошибку функции
Reverse, когда строка переворачивается, можно проанализировать количество символов в перевернутой
строке.
Напишите код
В вашем текстовом редакторе замените целевой объект fuzz внутри FuzzReverse следующим:
<code>f.Fuzz(func(t *testing.T, orig string) {
rev := Reverse(orig)
doubleRev := Reverse(rev)
t.Logf("Number of runes: orig=%d, rev=%d, doubleRev=%d", utf8.RuneCountInString(orig), utf8.RuneCountInString(rev), utf8.RuneCountInString(doubleRev))
if orig != doubleRev {
t.Errorf("Before: %q, after: %q", orig, doubleRev)
}
if utf8.ValidString(orig) && !utf8.ValidString(rev) {
t.Errorf("Reverse produced invalid UTF-8 string %q", rev)
}
})
</code>
This t.Logf line will print to the command line if an error occurs, or if
executing the test with -v, which can help you debug this particular issue.
Запустите код
Запустите тест с помощью команды go test
<code>$ go test --- FAIL: FuzzReverse (0.00s) --- FAIL: FuzzReverse/28f36ef487f23e6c7a81ebdaa9feffe2f2b02b4cddaa6252e87f69863046a5e0 (0.00s) reverse_test.go:16: Number of runes: orig=1, rev=3, doubleRev=1 reverse_test.go:21: Reverse produced invalid UTF-8 string "\x83\xb3\xe6" FAIL exit status 1 FAIL example/fuzz 0.598s </code>
Весь используемый набор данных (seed corpus) содержал строки, в которых каждый символ был представлен одним байтом. Однако символы, такие как 泃, могут требовать нескольких байтов. Таким образом, разворот строки по байтам приведёт к повреждению многобайтовых символов.
Примечание: Если вы заинтересованы в том, как Go работает со строками, ознакомьтесь с блог-постом Strings, bytes, runes and characters in Go для более глубокого понимания.
Имея лучшее понимание ошибки, исправьте её в функции Reverse.
Исправьте ошибку
Чтобы исправить функцию Reverse, давайте теперь обойдём строку по символам (runes), а не по байтам.
Напишите код
В вашем текстовом редакторе замените существующую функцию Reverse() следующим кодом.
<code>func Reverse(s string) string {
r := []rune(s)
for i, j := 0, len(r)-1; i < len(r)/2; i, j = i+1, j-1 {
r[i], r[j] = r[j], r[i]
}
return string(r)
}
</code>
Ключевое отличие заключается в том, что теперь функция Reverse проходит по каждому rune
в строке, а не по каждому byte. Обратите внимание, что это лишь пример, и он не корректно
обрабатывает символы
с диакритикой.
Запустите код
-
Запустите тест с помощью команды
go test<code>$ go test PASS ok example/fuzz 0.016s </code>
Тест теперь проходит успешно!
-
Повторно протестируйте с помощью
go test -fuzz, чтобы проверить наличие новых ошибок.<code>$ go test -fuzz=Fuzz fuzz: elapsed: 0s, gathering baseline coverage: 0/37 completed fuzz: minimizing 506-byte failing input file... fuzz: elapsed: 0s, gathering baseline coverage: 5/37 completed --- FAIL: FuzzReverse (0.02s) --- FAIL: FuzzReverse (0.00s) reverse_test.go:33: Before: "\x91", after: "�" Failing input written to testdata/fuzz/FuzzReverse/1ffc28f7538e29d79fce69fef20ce5ea72648529a9ca10bea392bcff28cd015c To re-run: go test -run=FuzzReverse/1ffc28f7538e29d79fce69fef20ce5ea72648529a9ca10bea392bcff28cd015c FAIL exit status 1 FAIL example/fuzz 0.032s </code>
Можно увидеть, что строка отличается от исходной после двойного разворота. На этот раз входные данные сами по себе являются недопустимыми юникодными символами. Как это возможно, если мы тестируем с помощью строк?
Давайте снова отладим ситуацию.
Исправьте ошибку двойного разворота
В этом разделе вы будете отлаживать ошибку двойного разворота и исправлять баг.
Не стесняйтесь потратить некоторое время на размышления и попытки самостоятельно исправить проблему перед тем, как продолжить.
Диагностика ошибки
Как и прежде, существует несколько способов отладки этого сбоя. В данном случае, использование отладчика будет отличным подходом.
В этом руководстве мы будем логировать полезную информацию для отладки внутри функции Reverse.
Внимательно посмотрите на развернутую строку, чтобы обнаружить ошибку. В Go, строка представляет собой неизменяемый срез байтов
Строка может содержать байты, которые не являются допустимыми UTF-8. Исходная строка — это байтовый срез с одним
байтом, '\x91'. Когда строка ввода устанавливается в []rune, Go кодирует байтовый срез
в UTF-8 и заменяет байт символом �. При сравнении замещающего символа UTF-8 с исходным байтовым срезом они
clearly не равны.
Напишите код
-
В вашем текстовом редакторе замените функцию
Reverseследующим кодом.<code>func Reverse(s string) string { fmt.Printf("input: %q\n", s) r := []rune(s) fmt.Printf("runes: %q\n", r) for i, j := 0, len(r)-1; i < len(r)/2; i, j = i+1, j-1 { r[i], r[j] = r[j], r[i] } return string(r) } </code>Это поможет нам понять, что происходит при преобразовании строки в срез рун.
Запустите код
На этот раз мы хотим запустить только тест, который не проходит, чтобы проанализировать логи. Для этого мы будем
использовать go test -run.
Чтобы запустить конкретный элемент корпуса в FuzzXxx/testdata, вы можете передать {FuzzTestName}/{filename} в
-run. Это может быть полезно при отладке. В данном случае установите флаг -run равным
точному хэшу теста, который не прошёл. Скопируйте и вставьте уникальный хэш из вашего терминала; он будет
отличаться от приведённого ниже.
<code>$ go test -run=FuzzReverse/28f36ef487f23e6c7a81ebdaa9feffe2f2b02b4cddaa6252e87f69863046a5e0 input: "\x91" runes: ['�'] input: "�" runes: ['�'] --- FAIL: FuzzReverse (0.00s) --- FAIL: FuzzReverse/28f36ef487f23e6c7a81ebdaa9feffe2f2b02b4cddaa6252e87f69863046a5e0 (0.00s) reverse_test.go:16: Number of runes: orig=1, rev=1, doubleRev=1 reverse_test.go:18: Before: "\x91", after: "�" FAIL exit status 1 FAIL example/fuzz 0.145s </code>
Зная, что входные данные содержат недопустимую кодировку Unicode, давайте исправим ошибку в функции
Reverse.
Исправьте ошибку
Чтобы исправить эту проблему, вернём ошибку, если входные данные функции Reverse не являются
допустимыми UTF-8.
Напишите код
-
В вашем текстовом редакторе замените существующую функцию
Reverseследующим кодом.<code>func Reverse(s string) (string, error) { if !utf8.ValidString(s) { return s, errors.New("input is not valid UTF-8") } r := []rune(s) for i, j := 0, len(r)-1; i < len(r)/2; i, j = i+1, j-1 { r[i], r[j] = r[j], r[i] } return string(r), nil } </code>Это изменение вернёт ошибку, если входная строка содержит символы, не являющиеся допустимыми UTF-8.
-
Поскольку функция
Reverseтеперь возвращает ошибку, измените функциюmain, чтобы игнорировать дополнительное значение ошибки. Замените существующую функциюmainследующим кодом.<code>func main() { input := "The quick brown fox jumped over the lazy dog" rev, revErr := Reverse(input) doubleRev, doubleRevErr := Reverse(rev) fmt.Printf("original: %q\n", input) fmt.Printf("reversed: %q, err: %v\n", rev, revErr) fmt.Printf("reversed again: %q, err: %v\n", doubleRev, doubleRevErr) } </code>Вызовы функции
Reverseдолжны возвращать nil ошибку, поскольку входная строка является допустимой UTF-8. -
Вам нужно будет импортировать пакеты errors и unicode/utf8. Инструкция импорта в main.go должна выглядеть следующим образом.
<code>import ( "errors" "fmt" "unicode/utf8" ) </code>
-
Измените файл reverse_test.go для проверки ошибок и пропуска теста, если ошибки генерируются возвращением.
<code>func FuzzReverse(f *testing.F) { testcases := []string {"Hello, world", " ", "!12345"} for _, tc := range testcases { f.Add(tc) // Use f.Add to provide a seed corpus } f.Fuzz(func(t *testing.T, orig string) { rev, err1 := Reverse(orig) if err1 != nil { return } doubleRev, err2 := Reverse(rev) if err2 != nil { return } if orig != doubleRev { t.Errorf("Before: %q, after: %q", orig, doubleRev) } if utf8.ValidString(orig) && !utf8.ValidString(rev) { t.Errorf("Reverse produced invalid UTF-8 string %q", rev) } }) } </code>Вместо возврата можно также вызвать
t.Skip(), чтобы остановить выполнение этого фазз-входа.
Запустите код
-
Запустите тест с помощью команды go test
<code>$ go test PASS ok example/fuzz 0.019s </code>
-
Fuzz it с помощью
go test -fuzz=Fuzz, а затем, через несколько секунд, остановите процесс с помощьюctrl-C. Тест будет выполняться до тех пор, пока не будет обнаружен сбой, если только вы не передадите флаг-fuzztime. По умолчанию процесс продолжается бесконечно, если сбоев не происходит, и его можно прервать с помощьюctrl-C.
<code>$ go test -fuzz=Fuzz fuzz: elapsed: 0s, gathering baseline coverage: 0/38 completed fuzz: elapsed: 0s, gathering baseline coverage: 38/38 completed, now fuzzing with 4 workers fuzz: elapsed: 3s, execs: 86342 (28778/sec), new interesting: 2 (total: 35) fuzz: elapsed: 6s, execs: 193490 (35714/sec), new interesting: 4 (total: 37) fuzz: elapsed: 9s, execs: 304390 (36961/sec), new interesting: 4 (total: 37) ... fuzz: elapsed: 3m45s, execs: 7246222 (32357/sec), new interesting: 8 (total: 41) ^Cfuzz: elapsed: 3m48s, execs: 7335316 (31648/sec), new interesting: 8 (total: 41) PASS ok example/fuzz 228.000s </code>
-
Выполните fuzz-тест с помощью
go test -fuzz=Fuzz -fuzztime 30s, который будет выполнять fuzz-тестирование в течение 30 секунд перед завершением, если не будет найдено никаких сбоев.<code>$ go test -fuzz=Fuzz -fuzztime 30s fuzz: elapsed: 0s, gathering baseline coverage: 0/5 completed fuzz: elapsed: 0s, gathering baseline coverage: 5/5 completed, now fuzzing with 4 workers fuzz: elapsed: 3s, execs: 80290 (26763/sec), new interesting: 12 (total: 12) fuzz: elapsed: 6s, execs: 210803 (43501/sec), new interesting: 14 (total: 14) fuzz: elapsed: 9s, execs: 292882 (27360/sec), new interesting: 14 (total: 14) fuzz: elapsed: 12s, execs: 371872 (26329/sec), new interesting: 14 (total: 14) fuzz: elapsed: 15s, execs: 517169 (48433/sec), new interesting: 15 (total: 15) fuzz: elapsed: 18s, execs: 663276 (48699/sec), new interesting: 15 (total: 15) fuzz: elapsed: 21s, execs: 771698 (36143/sec), new interesting: 15 (total: 15) fuzz: elapsed: 24s, execs: 924768 (50990/sec), new interesting: 16 (total: 16) fuzz: elapsed: 27s, execs: 1082025 (52427/sec), new interesting: 17 (total: 17) fuzz: elapsed: 30s, execs: 1172817 (30281/sec), new interesting: 17 (total: 17) fuzz: elapsed: 31s, execs: 1172817 (0/sec), new interesting: 17 (total: 17) PASS ok example/fuzz 31.025s </code>
Fuzz-тест пройден!
В дополнение к флагу
-fuzz, вgo testбыли добавлены несколько новых флагов, которые можно просмотреть в документации.Для получения дополнительной информации о терминах, используемых в fuzz-тестировании, обратитесь к разделу Go Fuzzing. Например, «new interesting» (новые интересные) обозначает входные данные, которые расширяют покрытие кода существующего набора fuzz-тестов. Количество «новых интересных» входных данных может резко увеличиваться при начале fuzz-тестирования, затем возрастать несколько раз по мере обнаружения новых путей выполнения кода и, в конечном итоге, снижаться со временем.
Заключение
Отличная работа! Вы только что познакомились с fuzz-тестированием в Go.
Следующим шагом будет выбор функции в вашем коде, которую вы хотите протестировать с помощью fuzz-тестирования, и попытка применить его! Если fuzz-тестирование найдет ошибку в вашем коде, рассмотрите возможность добавления её в trophy case.
Если вы столкнётесь с какими-либо проблемами или у вас появится идея для новой функции, создайте запрос.
Для обсуждения и получения общих отзывов о функции, вы также можете участвовать в #fuzzing канале в Gophers Slack.
Ознакомьтесь с документацией на go.dev/security/fuzz для дальнейшего чтения.
Завершённый код
— main.go —
<code>package main
import (
"errors"
"fmt"
"unicode/utf8"
)
func main() {
input := "The quick brown fox jumped over the lazy dog"
rev, err1 := Reverse(input)
doubleRev, err2 := Reverse(rev)
fmt.Printf("original: %q\n", input)
fmt.Printf("reversed: %q, err: %v\n", rev, err1)
fmt.Printf("reversed again: %q, err: %v\n", doubleRev, err2)
}
func Reverse(s string) (string, error) {
if !utf8.ValidString(s) {
return s, errors.New("input is not valid UTF-8")
}
r := []rune(s)
for i, j := 0, len(r)-1; i < len(r)/2; i, j = i+1, j-1 {
r[i], r[j] = r[j], r[i]
}
return string(r), nil
}
</code>
— reverse_test.go —
<code>package main
import (
"testing"
"unicode/utf8"
)
func FuzzReverse(f *testing.F) {
testcases := []string{"Hello, world", " ", "!12345"}
for _, tc := range testcases {
f.Add(tc) // Используйте f.Add для предоставления начального набора данных
}
f.Fuzz(func(t *testing.T, orig string) {
rev, err1 := Reverse(orig)
if err1 != nil {
return
}
doubleRev, err2 := Reverse(rev)
if err2 != nil {
return
}
if orig != doubleRev {
t.Errorf("Before: %q, after: %q", orig, doubleRev)
}
if utf8.ValidString(orig) && !utf8.ValidString(rev) {
t.Errorf("Reverse produced invalid UTF-8 string %q", rev)
}
})
}
</code>