Удалённые настройки локали в тестах агентов
Удалённые настройки локали меняют даты, сортировку, дроби и декодирование текста. Фиксируйте среду, записывайте её и тестируйте варианты.

Агент может запустить одну и ту же команду с теми же аргументами и файлами, но получить на удалённом хосте другой результат. Неучтённым входным параметром часто оказывается локаль процесса. Она меняет то, как привычные утилиты классифицируют текст, сортируют имена, форматируют числа, печатают даты, выбирают кодировку и формулируют ошибки.
Считайте локаль тестовыми данными, а не декоративной настройкой машины. Фиксируйте её, когда тест ожидает стабильный протокол, меняйте, когда код обещает учитывать человеческие правила, и записывайте при каждом переходе агента через границу процесса или SSH. Иначе зелёный локальный тест может скрывать ошибку удалённого разбора до тех пор, пока агент не выполнит действие с неверной строкой, суммой или датой.
Это не повод везде принудительно включать английский. У машинного и человеческого интерфейсов разные задачи. Стабильному машинному выводу нужны явный формат и среда. Пользовательский вывод нужно локализовать осознанно. Если смешать эти интерфейсы, переведённое диагностическое сообщение начнут разбирать как состояние, а десятичная запятая незаметно превратится в неверное значение.
Результат определяет удалённый процесс
Эффективная локаль принадлежит процессу, который выполняет команду. Локаль ноутбука не управляет программой, запущенной через SSH, если переменные не пересылаются или не задаются явно. Интерактивная удалённая оболочка тоже может отличаться от неинтерактивного сеанса команд.
POSIX.1-2024 чётко задаёт приоритет. Непустая переменная LC_ALL переопределяет все категории. Если её нет, переменная категории, например LC_TIME или LC_COLLATE, действует для своей категории. LANG задаёт значение по умолчанию для оставшихся категорий. Поэтому LANG=C ничего не меняет, пока в среде остаётся унаследованная LC_ALL=de_DE.UTF-8.
OpenSSH добавляет ещё одну границу. В руководстве клиента сказано, что SendEnv выбирает локальные переменные для отправки, но сервер должен их принять. По умолчанию клиент не отправляет ни одной. SetEnv позволяет запросить явные значения, однако сервер тоже должен их принять. В личной конфигурации SSH разработчика может быть SendEnv LANG LC_*, а SSH-помощник агента без состояния эту конфигурацию не использует. Возможна и обратная ситуация. Нельзя заранее полагаться ни на один вариант.
Файлы запуска при входе ещё сильнее усложняют картину. Дистрибутив может задавать LANG через PAM или системные настройки. Профиль пользователя может менять его при интерактивном входе. Удалённая команда часто запускается по другому пути. Запущенный ею контейнер может добавить свой набор локалей, а в минимальном образе нужная локаль иногда вообще не установлена.
Исправление простое: задавайте нужную среду в конечной точке выполнения. Если команде нужна стабильная локаль, не полагайтесь на пересылку. Поместите присваивание рядом с утилитой:
ssh buildbox 'env LC_ALL=C.UTF-8 TZ=UTC command-to-test --format=plain'
Так контракт теста виден сразу. Если C.UTF-8 недоступна, ошибка возникает в понятном месте, а не маскируется молчаливым наследованием настройки хоста. Когда это имя нельзя гарантировать, проверяйте доступные локали при подготовке системы и используйте документированный запасной вариант.
Записывайте среду до разбора вывода
Отчёт об удалённой ошибке должен содержать эффективные категории, кодировку, часовой пояс, сведения об утилите и исходные байты. Одной LANG недостаточно, потому что её может переопределить LC_ALL или переменная категории. Если сохранить только декодированный текст, доказательства ошибки кодировки могут исчезнуть.
Запустите небольшой диагностический набор перед исследуемой командой:
env | LC_ALL=C sort | sed -n '/^LANG=/p;/^LC_/p;/^TZ=/p'
printf 'charmap='; locale charmap
printf 'decimal='; locale -k decimal_point 2>/dev/null || true
printf 'date='; date +'%Y-%m-%dT%H:%M:%S%z'
printf 'tool='; command -v sort
sort --version 2>/dev/null | sed -n '1p'
Типичный результат Linux может выглядеть так:
LANG=de_DE.UTF-8
LC_NUMERIC=de_DE.UTF-8
TZ=Europe/Berlin
charmap=UTF-8
decimal=decimal_point=","
date=2026-07-24T143105+0200
tool=/usr/bin/sort
sort (GNU coreutils) 9.5
Не превращайте этот пример в ожидаемое значение. Важен набор полей. Некоторые реализации locale иначе форматируют вывод ключевых слов, а другие утилиты могут не поддерживать --version. Сохраняйте статус завершения и стандартный поток ошибок каждого запроса, чтобы отсутствие функции не выглядело пустым значением.
При ошибках кодировки сохраняйте байты до декодирования. Тестовая обвязка может записать стандартный вывод и ошибки в разные файлы, вычислить контрольные суммы, а затем декодировать копию с заявленной кодировкой. Шестнадцатеричный фрагмент вокруг первого недопустимого байта намного полезнее символа замены, который вставил слой журналирования.
Записывайте и точный способ передачи команды. ssh host command, ssh host sh -lc command, интерактивный терминал и процесс, запущенный инструментом агента, идут по разным путям. Они могут выбрать разные оболочки, файлы запуска, псевдотерминалы и фильтры среды. Если ошибка возникает в действии агента, воспроизводите этот путь, а не доказывайте, что команда работает при ручном входе.
Такой диагностический набор должен попадать в артефакт теста при любом расхождении. Он превращает фразу «удалённая сортировка иногда скачет» в сравнение конкретных входных параметров.
Фиксируйте локаль для протоколов, а не для людей
Используйте фиксированную локаль, когда вывод команды поступает в парсер, снимок, сравнение, ключ кеша, решение о развёртывании или другую программу. Когда вывод предназначен человеку, используйте нужную ему локаль. Это разные интерфейсы, даже если сейчас одна команда создаёт оба.
Совет везде задавать LC_ALL=C популярен, потому что он делает многие утилиты Unix предсказуемыми и доступен в системах POSIX. В качестве общего правила он неверен. В зависимости от системы и среды выполнения локаль C может предполагать модель символов с ориентацией на ASCII. Тогда программа, читающая имена вроде Málaga, может отвергнуть или неверно обработать байты, хотя порядок уже стабилен.
На многих современных системах Unix C.UTF-8 сочетает простую сортировку с UTF-8, поэтому хорошо подходит для тестов. Но POSIX не требует именно такого имени. macOS, дистрибутивы Linux, контейнеры и среды языков предоставляют разные каталоги локалей. locale -a показывает доступное на хосте, а подготовленный тестовый образ должен указывать, какое имя он гарантирует.
Нужно различать ещё две вещи: стабильность локали и стабильность формата вывода. Фиксация LC_ALL не обещает, что две версии утилиты напечатают одинаковые столбцы, пробелы, предупреждения или поля JSON. Если утилита предлагает JSON, разделители NUL, секунды эпохи или явную строку формата, выбирайте и этот интерфейс. Контроль локали убирает одну переменную, но не замораживает программу.
Хорошая обёртка удаляет возможные переопределения и добавляет только нужные значения:
run_stable() {
env -u LANGUAGE -u LC_COLLATE -u LC_CTYPE -u LC_MESSAGES \
-u LC_MONETARY -u LC_NUMERIC -u LC_TIME \
LC_ALL=C.UTF-8 TZ=UTC "$@"
}
run_stable sort input.txt
Если код должен работать в системах, где env не поддерживает -u, создайте минимальную среду. Явно задайте PATH и сохраните только нужные приложению переменные. Не копируйте всю родительскую среду с последующей заменой LANG, иначе в ней останутся переопределения категорий.
Тесты пользовательского поведения должны поступать наоборот. Они намеренно выбирают поддерживаемую локаль и проверяют нужное правило. Тест отчёта на немецком может ожидать десятичную запятую и немецкие названия месяцев. Парсер за этим отчётом всё равно должен обмениваться нормализованными числами и датами.
Датам нужны и формат, и часовой пояс
Локаль и часовой пояс вызывают разные ошибки дат. LC_TIME управляет названиями и принятыми представлениями. TZ определяет, какое гражданское время соответствует моменту. Фиксация одного не фиксирует другое.
GNU Coreutils предупреждает, что вывод date не всегда пригоден для последующего разбора. Руководство рекомендует для создаваемых данных формат, не зависящий от языка, григорианское представление и однозначную зону, например UTC или Z. Этот совет надёжнее снимка, который случайно прошёл с английскими настройками.
Для тестового протокола выберите явное представление:
env LC_ALL=C.UTF-8 TZ=UTC date +'%Y-%m-%dT%H:%M:%SZ'
Вывод имеет форму 2026-07-24T12:31:05Z. Если тесту нужен фиксированный момент, а не текущее время, передайте его поддерживаемым параметром утилиты или внедрите часы в приложение. Управление локалью не останавливает время.
Не используйте %c, %x, %X, %a и %b в данных, которые разбирает другая программа. Эти спецификаторы специально запрашивают региональные правила или переведённые названия. Даже числовые директивы в некоторых системах учитывают особенности календаря. Руководство GNU описывает локали, использующие другие календари для отдельных директив, поэтому внешне числовой год не становится универсальным контрактом без точного формата и выбранной локали.
Номера недель создают ещё одну ловушку. Календарный год, год ISO по неделям и местные правила отвечают на разные вопросы рядом с Новым годом. Укажите, какой вариант нужен бизнес-правилу, и проверьте пограничные даты. Фиксация локали не исправит ошибочную комбинацию %Y-%V.
В отчётах для людей форматируйте даты на границе системы. Храните и передавайте момент в стабильном виде, а затем применяйте локаль и пояс читателя при отображении. Если агент сравнивает отметки нескольких хостов, запрашивайте секунды эпохи или строки в стиле RFC 3339 со смещениями. Не заставляйте его угадывать, означает ли 03/04/26 3 апреля или 4 марта.
Полезная матрица дат включает локаль с английскими месяцами, локаль с другими названиями, UTC, пояс с переходом на летнее время и даты рядом с переводом часов и границей года. Не нужно перечислять весь мир. Нужно обнаружить код, который принял привычки разработчика за общее правило.
Сортировка должна соответствовать потребителю
У текста нет единственного естественного порядка. Порядок байтов, кодовых точек Unicode и языковая сортировка создают разные последовательности. Тест ломается, когда ожидает один порядок, а вызывает другой.
В руководстве GNU sort сказано, что сравнение обычно использует последовательность из LC_COLLATE. Для традиционного порядка скриптам прямо рекомендуют LC_ALL=C. Там же отмечено, что задавать только LC_COLLATE опасно, если её переопределяет LC_ALL или категории символов используют несовместимые кодировки.
Рассмотрим такой набор:
Zebra
apple
zebra
Ångström
ábaco
Локаль в стиле C обычно сортирует по закодированным байтам: прописные ASCII идут перед строчными, а последовательности UTF-8 вне ASCII позже. Языковая локаль может сравнивать регистр и ударения на разных уровнях. Не вставляйте предполагаемый порядок в кроссплатформенную статью или тест. Запустите реально поддерживаемую локаль и проверьте нужное семантическое свойство.
Для воспроизводимого манифеста или эталонного файла обычно подходит порядок байтов. Задайте LC_ALL=C, если все пути ограничены переносимым набором символов, либо используйте проверенную локаль UTF-8 и определите функцию сортировки в программе. Для списка, который видят испанские, шведские или немецкие читатели, бинарный порядок неудобен. Используйте библиотеку языковой сортировки с фиксированной версией данных, потому что системные таблицы могут обновиться без изменения вашего кода.
Сортировка и объединение должны использовать одинаковые правила. GNU Coreutils советует запускать sort и join с согласованными локалями и параметрами. Файл, отсортированный по одним правилам, может показаться join неотсортированным при других, что приведёт к пропущенным совпадениям или диагностике. То же относится к comm, удалению дублей, слиянию и любому конвейеру, который ожидает соседства равных значений.
Проверки должны выражать намерение. Если порядок не важен, сравнивайте множества или словари, а не снимок случайного порядка. Если протокол требует порядка байтов, вычислите его в тесте и подпишите. Если функция должна давать локализованный порядок, добавьте примеры с диакритикой, разным регистром и пунктуацией, чтобы отличить его от бинарной сортировки.
Агент может усугубить проблему: выполнить исследовательский sort, а затем считать первую строку «наименьшим» или «следующим» элементом. Внесите правило сортировки в контракт действия. «Выбрать первый релиз по семантической версии» и «выбрать первое имя файла по удалённой локали» не равнозначны.
Десятичные запятые незаметно ломают конвейеры
LC_NUMERIC задаёт десятичный знак и правила группировки для функций, учитывающих локаль, и некоторых параметров команд. Значение 1,25 может быть правильным для человека и недопустимым для парсера, ожидающего 1.25. Хуже того, снисходительный парсер иногда принимает только префикс и возвращает 1 без явной ошибки.
GNU sort -n использует разделитель тысяч и десятичный знак локали при распознавании числового префикса. Функции Python locale.format_string, locale.atof и связанные с ними тоже следуют LC_NUMERIC. Обычная float() в Python и многие форматы данных этого не делают. Передача текста между двумя группами без чёткой границы создаёт ошибку, которая проявляется только с отдельными настройками.
Оставляйте числа протокола нормализованными. Числа JSON используют точку, параметры команд обычно описывают фиксированную грамматику, а форматы баз данных задают собственное представление. Форматируйте запятую и группы только для показа, после вычислений и сериализации.
Проверяйте парсеры значениями, на которых хорошо видно молчаливое усечение:
0.5
1.25
1234.75
-0.125
Затем выполните ту же операцию в локали с десятичной запятой. Если утилита специально принимает локализованный ввод, добавьте эквивалентные примеры с запятой и отвергайте неоднозначную группировку. Если заявлена фиксированная грамматика, задайте локаль команды и проверьте, что ввод с запятой завершается понятной ошибкой.
Не «исправляйте» произвольный вывод заменой всех запятых на точки. Запятая может разделять поля, группировать тысячи или быть частью текста. Используйте структурированный режим вывода или парсер, который знает заявленную локаль. Если производитель не публикует грамматику, считайте человеческий вывод непригодным для автоматизации.
Арифметика shell тоже создаёт ложную уверенность. Сама оболочка может использовать фиксированный синтаксис, а вызванные awk, printf, конвертер таблиц или языковая среда применяют локаль лишь в отдельных операциях. Тестируйте весь конвейер в одной среде, а не каждую команду отдельно в оболочке входа.
Деньги требуют ещё более строгого подхода. Храните минимальные денежные единицы или десятичный тип с явной валютой, а локализуйте только показанное значение. Агент, который проверяет превышение лимита, должен получить нормализованное число, а не извлекать его из отчёта для человека.
Ошибки кодировки начинаются до декодирования
Локаль может указывать процессу, как интерпретировать последовательности байтов как символы. LC_CTYPE влияет на классификацию символов и часто связана с набором кодировки. Это важно для утилит, которые делят текст, сопоставляют классы, меняют регистр, вычисляют ширину или преобразуют байты и строки.
Наличие UTF-8 на обеих машинах не доказывает, что каждый процесс использует UTF-8. Удалённая служба может стартовать в локали C, минимальный контейнер может не содержать сгенерированных данных, а языковая среда может включить собственный режим UTF-8. Документация Python говорит прямо: на некоторых системах предпочтительная кодировка остаётся лишь предположением, а Python UTF-8 Mode может игнорировать кодировку локали при таком запросе.
Декодируйте явно на границах. Если контракт команды говорит UTF-8, читайте байты и строго декодируйте их как UTF-8. Не вызывайте декодер платформы по умолчанию в надежде на удачу. Если в Unix возможны произвольные имена файлов, помните, что на границе ОС это последовательности байтов. Принудительное превращение в обычный текст может потерять сведения. Используйте специальную кодировку файловой системы и обратимую стратегию ошибок среды, когда они доступны.
Замена ошибок полезна для отображения, но опасна для решений. Две разные недопустимые последовательности могут превратиться в один видимый символ замены. Тест должен сообщить смещение байта, сохранить исходный вывод и показать короткий шестнадцатеричный фрагмент. Эти данные позволяют понять, выдал ли источник старую кодировку, оборвал последовательность или вернул двоичные данные по текстовому каналу.
Классы символов требуют прямых примеров. В разных локалях [[:alpha:]], изменение регистра и распознавание пробелов могут включать разные символы. POSIX объясняет, что LC_CTYPE определяет преобразование байтов в символы и принадлежность символов к классам. Скрипт, очищающий имена диапазоном, зависящим от локали, может на удалённом хосте принять или удалить другой текст.
Для человеческого текста используйте приложение с поддержкой Unicode, для идентификаторов протокола задавайте явные правила ASCII. Не позволяйте локали среды решать, что может быть именем переменной, токеном или полем протокола. И наоборот, не применяйте фильтр только ASCII к имени человека и не называйте результат проверкой.
Компактный набор тестов кодировки должен включать простой ASCII, предварительно составленный текст с диакритикой, тот же видимый текст с комбинируемыми знаками, другую письменность и намеренно недопустимую последовательность байтов, если интерфейс принимает байты. Проверяйте байты на границе протокола и символы после декодирования. Такое разделение делает сбой понятным.
Небольшая матрица обнаруживает скрытые допущения
Запускайте большинство детерминированных тестов с фиксированной локалью, а затем меньший набор вариантов, специально выбранных для разрушения допущений. Проверять все установленные локали дорого и малоэффективно, поскольку многие из них разделяют одни и те же важные правила.
Выбирайте варианты по поведению:
- Используйте
Cдля переносимого поведения байтов и переведённых сообщений, которые нельзя разбирать. - Возьмите доступную локаль UTF-8 с десятичной точкой и нетривиальной сортировкой.
- Возьмите локаль UTF-8 с десятичной запятой и другими названиями дат.
- Добавьте локаль или режим среды, обнаруживающий допущения о кодировке, если продукт это поддерживает.
- Сочетайте тесты дат с UTC и поясом, где переводят часы.
Установите эти локали в тестовом образе. Пропуск теста из-за отсутствия данных у runner не считается успехом. При ошибке подготовки печатайте locale -a и включайте нужный каталог в определение образа.
Держите матрицу рядом с запуском процесса. В Python передавайте копию среды в subprocess.run, а не меняйте глобальную локаль в многопоточном runner:
import os
import subprocess
def run_case(locale_name):
child_env = os.environ.copy()
child_env.update({"LC_ALL": locale_name, "TZ": "UTC"})
return subprocess.run(
["./agent-command", "inspect", "fixtures/names.txt"],
env=child_env,
check=False,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
Руководство Python указывает, что setlocale() в большинстве систем небезопасна для потоков и меняет свойство всей программы. Переключение между тестами приводит к взаимному влиянию параллельных случаев. Среда дочернего процесса изолирует команду и точнее повторяет удалённое выполнение.
Проверяйте отдельно статус завершения, байты стандартного вывода, байты ошибок и разобранный смысл. LC_MESSAGES может поменять язык диагностики без изменения ошибки. Тест, ищущий английскую фразу «No such file», проверяет каталог перевода, а не условие ошибки. Предпочитайте статусы, структурированные поля или стабильные идентификаторы.
Если вариант не прошёл, сужайте причину по категориям. Сначала очистите LC_ALL, затем задавайте LANG и отдельные категории, чтобы найти влияние времени, сортировки, чисел, сообщений или символов. Переменные категорий остаются отличными диагностическими инструментами, даже если в рабочей среде используется одна LC_ALL.
Запускайте небольшую матрицу для изменений разбора, запуска процессов, SSH, отчётов или тестовых данных. Плановый запуск может охватить больше ОС и версий утилит. Сохраняйте диагностику среды рядом с каждой ошибкой, чтобы повтор не зависел от памяти.
Действиям агента нужен контракт выполнения
Автономный агент усиливает неоднозначность локали, потому что может связать правдоподобный вывод с действием, имеющим последствия. Если список отсортирован иначе, агент выберет другой файл. Если парсер усекает дробь, он сравнит неверный лимит. Если дата пересечёт границу пояса, он обработает запись не за тот день.
Дайте инструментам удалённых команд явный контракт из четырёх частей: задаваемая среда, возвращаемые байты или структурированные данные, сохраняемая информация о завершении и семантика оболочки. Добавляйте версии утилит или проверки возможностей, когда вывод зависит от реализации. Промпт не исправит неописанную границу процесса.
Определите успех до того, как агент увидит вывод. Нулевой статус может означать завершение команды, но не нахождение записи. Некоторые утилиты возвращают частичный результат с предупреждением, другие печатают ход работы в поток ошибок даже при успехе. Сохраните все три канала, затем пусть парсер с заявленной грамматикой решит, пригоден ли результат. Не просите модель определять успех по тону локализованного сообщения.
Отсутствующая локаль должна вызывать ошибку подготовки, а не неожиданность во время действия. Если команда запускается с LC_ALL=fr_FR.UTF-8 на хосте без этой локали, оболочка или среда может предупредить и выбрать запасную, либо программа завершится ошибкой. Обвязка должна сначала подтвердить локаль через locale -a или проверку возможностей, записать имя и остановиться, если нужное поведение нельзя проверить. Запасной вариант, выбранный при подготовке, контролируем; выбранный посреди действия агента становится скрытым состоянием.
Отдельно проверяйте слои интерпретации. Локальный процесс строит аргумент SSH, удалённая служба запускает оболочку пользователя, а та разбирает строку до чтения аргументов целевой программой. Присваивания и кавычки могут измениться на любом слое. Если есть удалённый API с аргументами, предпочитайте его. Когда доступна только строка shell, проверяйте точную сериализацию и включайте пробелы, одинарную кавычку, перевод строки и текст вне ASCII. Фиксация локали не исправляет кавычки, но ошибка кавычек может применить локаль не к той команде.
Считайте часто разбираемый вывод небольшим версионируемым протоколом. Храните образец исходных байтов, документируйте ожидаемую локаль и семейство утилиты, отвергайте неизвестные формы. Когда обновление меняет форму, вместе обновляйте парсер и образец. Это выглядит скромнее, чем просьба к агенту «понять» новый человеческий формат, и намного безопаснее для команд, ведущих к записи.
Для SSH предпочитайте команду, которая задаёт среду удалённо, а не надежду на совпадение клиентской пересылки. Правильно расставляйте кавычки на каждом слое и проверяйте значения с пробелами, кавычками и текстом вне ASCII. Не добавляйте оболочку входа только ради локали, потому что она также импортирует псевдонимы, скрипты запуска и другое состояние.
Оставляйте исходные результаты для проверки. Sallyport может выполнять действия SSH через встроенный помощник sp-ssh, сохраняя ключи SSH в зашифрованном хранилище, а журнал Activity записывает отдельные вызовы. Это не делает вывод независимым от локали, но сохраняет полезную границу: агент получает результат и не получает использованные для него учётные данные.
Если действие меняет внешнее состояние, проверяйте разобранное значение до записи. Требуйте явный формат от команды чтения, отвергайте недекодируемые байты и прикладывайте записанную локаль к предлагаемому действию. Одобрение человеком имеет смысл, только когда карточка показывает значение, которое система действительно разобрала.
Первое исправление существующего набора вполне конкретно. Найдите все удалённые команды, чей вывод разбирается или сохраняется снимком. Добавьте диагностику среды к ошибкам, зафиксируйте локаль и часовой пояс удалённого процесса, затем внесите локаль с десятичной запятой и локаль с другой сортировкой как проверочные случаи. Неожиданные падения и есть те допущения, которые скрывала локальная оболочка.
Вопросы и ответы
Может ли SSH автоматически передать мою локаль на удалённый хост?
Только если клиент отправляет выбранные переменные, а сервер их принимает. По умолчанию OpenSSH не передаёт переменные среды, поэтому задавайте локаль в удалённой команде, а не рассчитывайте на пересылку.
Нужно ли использовать в тестах LC_ALL=C или C.UTF-8?
Используйте C, когда ввод ограничен переносимым ASCII, а контракт требует порядка байтов. Для UTF-8 выбирайте доступную локаль типа C.UTF-8, но проверяйте точное имя при подготовке, потому что POSIX его не требует.
Почему LANG=C не делает вывод стабильным?
Непустая LC_ALL переопределяет LANG, а переменные категорий меняют поведение своих категорий. Удалите конфликтующие переменные или задайте LC_ALL у реально запускаемого процесса.
Какие переменные влияют на сортировку и числа?
LC_COLLATE управляет сортировкой, а LC_NUMERIC задаёт десятичный знак и группировку в операциях с учётом локали. LC_CTYPE тоже важна, потому что сначала утилита должна интерпретировать символы.
Исправляет ли фиксация локали различия часовых поясов?
Нет. Задайте TZ отдельно и выберите явный формат даты. Стабильному тесту обычно нужны и локаль вроде C.UTF-8, и пояс вроде UTC.
Как тестировать локали, которых нет в образе CI?
Установите точный каталог локали в образ и завершайте подготовку ошибкой, если его нет. Вывод locale -a помогает диагностике, но молчаливый пропуск случая только скрывает риск.
Всегда ли вывод JSON независим от локали?
Грамматика JSON использует фиксированную пунктуацию, но производитель может поместить локализованные даты или числа в строковые поля. Проверяйте контракт полей и парсер, а не только фигурные скобки.
Зачем тестам сохранять исходные байты команды?
Декодер может заменить или удалить недопустимые последовательности до записи журнала. Исходные вывод и ошибки сохраняют доказательства, нужные для поиска первого неверного байта и определения кодировки.
Можно ли менять локаль процесса в параллельных тестах?
Лучше не делать этого. Многие среды хранят локаль как глобальное состояние процесса, а Python указывает, что setlocale() в большинстве систем небезопасна для потоков. Задавайте отдельную среду дочернему процессу.
Сколько вариантов локали должен запускать набор тестов?
Основной набор запускайте с одной фиксированной локалью, а небольшую матрицу выбирайте по поведению: порядок байтов, десятичная запятая, другие названия дат и UTF-8. Дополнительные имена бесполезны, если проверяют то же допущение.