В проекте тесты в режиме проверки гонок используют другую реализацию файла. Как это влияет на вывод о качестве основной реализации?
Результат тестов относится к той реализации, которая была выбрана при сборке. Режим проверки гонок не только инструментирует бинарник, но и устанавливает build-тег race, поэтому файлы с условиями race и !race могут взаимно заменять друг друга. Успешный тест такой версии не доказывает корректность реализации, собираемой без этого тега.
В Go build constraints появились как механизм выбора исходных файлов для разных платформ, архитектур и режимов сборки. Это позволяет отделять платформенный код и специальные реализации, не включая их одновременно в один пакет.
Режим проверки гонок использует тот же механизм: при соответствующей сборке становится доступен тег race. Поэтому проект может предоставить отдельную реализацию для этого режима, например с дополнительной диагностикой или другой стратегией синхронизации.
Если файл с тегом race содержит другую логику, тесты фактически проверяют не обычную production-реализацию, а альтернативную. Это может создать ложное ощущение, что дефект найден или устранён во всех вариантах сборки.
Обратная ситуация тоже возможна: обычная реализация покрыта тестами без режима проверки гонок, а реализация для race остаётся непроверенной. Дополнительная опасность возникает, если различия между файлами считаются чисто техническими, хотя они меняют наблюдаемое поведение.
При сборке с режимом проверки гонок Go устанавливает build-тег race. Файлы с условием race включаются, а файлы с условием !race исключаются. Одновременно компилятор и инструменты добавляют специальную поддержку для обнаружения конфликтующих обращений к памяти.
Минимальная схема выглядит так:
Эти файлы являются взаимоисключающими вариантами одного пакета. Тест, выполненный в режиме проверки гонок, подтверждает поведение варианта race и результат действия инструментирования, но не заменяет отдельную проверку обычной сборки.
Практически следует минимизировать различия между такими вариантами и не помещать в них бизнес-логику без необходимости. Если альтернативная реализация нужна, её следует тестировать отдельно в каждом поддерживаемом режиме, а критичные контракты — проверять общими тестами, которые запускаются в обоих вариантах.
Важно не путать build-тег race с самим фактом обнаружения гонки. Тег может использоваться проектом для выбора файлов, тогда как детектор гонок анализирует выполняющиеся обращения к памяти. Поэтому успешный результат означает отсутствие обнаруженных проблем в конкретной сборке и выполненных сценариях, но не эквивалентность двух реализаций.
В библиотеке счётчиков обычная реализация использовала компактную структуру, а вариант для проверки гонок — защищённую структуру с mutex. Все тесты в режиме проверки гонок проходили, но после выпуска обнаружилась ошибка в обычной реализации: она некорректно обновляла состояние при конкурентном доступе.
Рассматривались два варианта. Можно было считать тесты в режиме проверки гонок достаточными, но это не проверяло обычный код. Можно было полностью дублировать набор тестов для каждого файла, однако такое решение увеличивало стоимость сопровождения и риск расхождения сценариев.
Выбрали общие контрактные тесты, запускаемые для обеих реализаций, и отдельный запуск проверки гонок для конкурентных сценариев. В результате тесты проверяли каждую реализацию, а детектор гонок дополнительно анализировал исполняемый вариант с нужным инструментированием.
race отсутствуют?Нет. В этом случае действительно проверяется та же исходная реализация, но в другом режиме компиляции и исполнения. Инструментирование помогает обнаруживать гонки в выполненных сценариях, однако не гарантирует проверку всех возможных межпоточных интерливингов и не заменяет обычный запуск.
race изменить только тестовый код, не затронув production-пакет?Да. Условия сборки применяются к любым исходным файлам пакета, включая файлы, содержащие только тесты. Если альтернативный файл находится в тестовом пакете или имеет суффикс для тестов, различие может влиять на тестовую инфраструктуру, помощники и фикстуры, даже когда production-код не меняется.
Можно утверждать, что обе реализации удовлетворяют проверенным контрактам в соответствующих запусках. Нельзя утверждать, что они идентичны по производительности, внутренним гарантиям или поведению в непроверенных сценариях. Для таких свойств нужны отдельные тесты и, при необходимости, benchmarks для каждого варианта.