День 10 · Контрольная точка 2: проверить программу с вводом · страница 1 из 3

Проверить программу с вводом самому

Сегодня — контрольная точка блока 2, и шесть задач из восьми в ней — программы с вводом (задачи 1–5 и 8). Проверка курса прогоняет их на наборе вводов, часть которых скрыта: на скрытом случае видно, что подали и что напечатала программа, но не видно, что было нужно. Узнавать об ошибках от проверки по одной, с паузой в 15 секунд, долго. Тот же прогон делают сами, до course check. Новых конструкций Go сегодня нет.


Один удачный ввод ничего не доказывает

Ситуация. В ~/gocourse/day10/selftest — программа, которая считает посылки за смену. Условие — в TASK.txt той же папки. Пример из условия она проходит. Работает ли она?

Ошибки прячутся на краях — это правило дня 07. У программ с циклом есть ещё один край: цикл, который не выполнился ни разу. Тогда накопители остались с начальными значениями, и то, что после цикла на них опирается, получает ноль. Поэтому вводы подбирают по видам:

Вид ввода Что ловит Для selftest
обычный ошибки в самой формуле пример из TASK.txt
граница < против <=, счёт с 0 или с 1 одна посылка; для порога — ровно порог и на единицу по обе стороны
пустой цикл не выполнился: деление на ноль, минимум без значения пустой файл; файл из пустых строк
мусор нет проверки err, нет TrimSpace, сообщение не в тот поток строка 12 кг, отрицательное число

На каждое условие из TASK.txt — ввод ровно на границе; на каждый цикл по строкам — пустой ввод и одна строка; на каждый Atoi — не число.


Набор вводов в файлах

Файлы набирают один раз, а прогоняют после каждой правки. Держите их в папке cases рядом с программой:

Пример · только посмотреть, набирать не нужно
stagiaire@lab:~/gocourse/day10/selftest$ mkdir cases
stagiaire@lab:~/gocourse/day10/selftest$ printf '1200\n800\n2500\n' > cases/normal.txt
stagiaire@lab:~/gocourse/day10/selftest$ printf '700\n' > cases/one.txt
stagiaire@lab:~/gocourse/day10/selftest$ printf '' > cases/empty.txt
stagiaire@lab:~/gocourse/day10/selftest$ printf '1200\n12 кг\n' > cases/junk.txt
stagiaire@lab:~/gocourse/day10/selftest$ ls cases
empty.txt  junk.txt  normal.txt  one.txt

printf '' не печатает ничего, и > создаёт пустой файл.

Ожидаемое — до запуска. Для каждого файла запишите, что должно получиться и с каким кодом. Если сначала запустить, любой вывод кажется правдоподобным. Готовая таблица есть, но она стоит после блока ниже: сначала своя запись по TASK.txt, потом сверка.

Попробуйте сейчас: набор вводов для selftest.

Цель: папка cases с четырьмя файлами вводов и таблица ожидаемого на бумаге — до первого запуска.

1. Создайте cases и четыре файла, как выше:

▶ Выполните
cd ~/gocourse/day10/selftest
mkdir cases
printf '1200\n800\n2500\n' > cases/normal.txt
printf '700\n' > cases/one.txt
printf '' > cases/empty.txt
printf '1200\n12 кг\n' > cases/junk.txt
ls cases

2. Программу пока не запускайте: по TASK.txt заполните на бумаге таблицу «файл — вывод — код».

3. Что по условию должно получиться на двух пустых строках подряд, printf '\n\n'?

Готово, когда: ls cases печатает четыре имени — empty.txt, junk.txt, normal.txt, one.txt (как они разложены по строкам, зависит от ширины окна), на бумаге для каждого файла записаны вывод и код, и есть ответ про printf '\n\n'.

Сверьте со своей записью. По TASK.txt для этих четырёх файлов должно получиться так:

Файл Ожидаемый вывод Код
normal.txt Посылок: 3, Общий вес: 4500 г, Средний вес: 1500 г, Самая тяжёлая: 2500 г 0
one.txt одна посылка, все веса — 700 г 0
empty.txt Посылок нет 0
junk.txt в поток ошибок ошибка в строке 2, обычный вывод пуст 1

Разошлось — читайте TASK.txt ещё раз: дальше эта таблица работает как ответ, с которым сверяют вывод программы, и неверная строка в ней уведёт поиск дефекта не туда.


Все файлы одной командой

Прогоняют собранный файл, а не go run: при ненулевом коде go run печатает свою строку и сам выходит с кодом 1 (день 07):

Пример · только посмотреть, набирать не нужно
stagiaire@lab:~/gocourse/day10/selftest$ go run . < cases/junk.txt; echo "код: $?"
ошибка в строке 2
exit status 1
код: 1

Сборка и прогон по всем файлам:

Пример · только посмотреть, набирать не нужно
stagiaire@lab:~/gocourse/day10/selftest$ go build -o selftest .
stagiaire@lab:~/gocourse/day10/selftest$ for f in cases/*.txt; do echo "== $f"; ./selftest < $f; echo "код: $?"; done

Это цикл оболочки bash, а не Go: он перебирает не числа, а имена файлов.

Часть Что значит
for f in cases/*.txt * — любые символы: bash подставит все файлы .txt из cases по алфавиту, f по очереди получает каждое имя
do … done тело цикла; ; разделяет команды в строке
echo "== $f" заголовок: $f — имя текущего файла
./selftest < $f запуск с вводом из этого файла
echo "код: $?" код возврата программы

Что увидите.

Пример · только посмотреть, набирать не нужно
stagiaire@lab:~/gocourse/day10/selftest$ for f in cases/*.txt; do echo "== $f"; ./selftest < $f; echo "код: $?"; done
== cases/empty.txt
panic: runtime error: integer divide by zero

goroutine 1 [running]:
main.main()
	/home/stagiaire/gocourse/day10/selftest/main.go:37 +0x41f
код: 2
== cases/junk.txt
ошибка в строке 2
код: 1
== cases/normal.txt
Посылок: 3
Общий вес: 4500 г
Средний вес: 1500 г
Самая тяжёлая: 2500 г
код: 0
== cases/one.txt
Посылок: 1
Общий вес: 700 г
Средний вес: 700 г
Самая тяжёлая: 700 г
код: 0

Как это читать. Сверяем с таблицей ожидаемого. normal, one и junk совпали. На empty программа упала: panic — аварийная остановка, код 2. Пример из условия этого не показал бы. Где и почему — разберёте сами в задании ниже.


Сверка с ожидаемым

Сорок строк глазами не сверить. diff из дня 02 сравнивает файлы построчно: ожидаемое записывают в файл заранее, вывод программы — в другой файл через >.

Пример · только посмотреть, набирать не нужно
stagiaire@lab:~/gocourse/day10/selftest$ printf 'Посылок: 3\nОбщий вес: 4500 г\nСредний вес: 1500 г\nСамая тяжёлая: 2500 г\n' > cases/normal.want
stagiaire@lab:~/gocourse/day10/selftest$ ./selftest < cases/normal.txt > got.txt
stagiaire@lab:~/gocourse/day10/selftest$ diff got.txt cases/normal.want
stagiaire@lab:~/gocourse/day10/selftest$

Тишина — файлы совпали. Расширение .want, а не .txt: иначе cases/*.txt подал бы программе ожидаемый вывод как ввод.

Пример · только посмотреть, набирать не нужно
stagiaire@lab:~/gocourse/day10/selftest$ printf 'Посылок нет\n' > cases/empty.want
stagiaire@lab:~/gocourse/day10/selftest$ ./selftest < cases/empty.txt > got.txt
panic: runtime error: integer divide by zero

goroutine 1 [running]:
main.main()
	/home/stagiaire/gocourse/day10/selftest/main.go:37 +0x41f
stagiaire@lab:~/gocourse/day10/selftest$ diff got.txt cases/empty.want
0a1
> Посылок нет

Строка со знаком > есть только во втором файле, ожидаемом; со знаком < — только в первом, в выводе программы. 0a1 — служебная строка. Текст panic в got.txt не попал: это поток ошибок, > его не трогает. Поэтому в случаях с ошибкой обычный вывод сверяют файлом, а сообщение и код — на экране.


Попробуйте сейчас: задание selftest целиком.

Цель: свой набор вводов в cases, прогон по нему одной командой и починенная selftest.

1. Прочитайте условие:

▶ Выполните
cd ~/gocourse/day10/selftest
cat TASK.txt

2. Папка cases, в ней не меньше четырёх файлов .txt: обычный, граница, пустой, мусор.

3. Сборка и прогон командой for:

▶ Выполните
go build -o selftest .
for f in cases/*.txt; do echo "== $f"; ./selftest < $f; echo "код: $?"; done

4. Для одного файла — ожидаемое в .want и сверка через diff. Например, для normal.txt, как в разделе «Сверка с ожидаемым»:

▶ Выполните
printf 'Посылок: 3\nОбщий вес: 4500 г\nСредний вес: 1500 г\nСамая тяжёлая: 2500 г\n' > cases/normal.want
./selftest < cases/normal.txt > got.txt
diff got.txt cases/normal.want

5. Почините программу и прогоните набор ещё раз: снова go build -o selftest . и та же команда for — стрелка вверх достаёт её из истории.

6. Добавьте файл с пробелами вокруг числа. Что должно получиться?

7. Проверка:

▶ Выполните
course check 10

Готово, когда: после починки прогон for на cases/empty.txt печатает Посылок нет и код: 0, а в начале отчёта course check 10 зелёные пункты тренировки selfcases, t_loop, t_diff и selftest.


Починить и прогнать всё заново

Это разбор приёма, а не ответа: правку в selftest вы делаете сами, она входит в задание выше. Готовой починки здесь нет и дальше по странице не будет.

panic — это адрес и причина сразу: main.go:37 — строка, на которой программа упала, а текст рядом — чем именно. Начинайте оттуда и дальше три вопроса по порядку:

  1. Какое значение в этой строке оказалось не таким, как вы ждали?
  2. Почему именно на этом вводе — что к этой строке успело выполниться, а что нет? Пройдите программу от первой строки на том же вводе.
  3. Значит, эта строка стоит не там, где нужно, или не хватает проверки? Таблица «Собирается, но считает не то» на странице 2 называет дефекты блока по признаку — найдите в ней свой.

Починка — правка в одну-две строки: это дефект в одной строке, а не переписанная программа. Что именно поменяли, покажет diff с оригиналом заготовки:

▶ Выполните
diff ~/.course/orig/day10/selftest/main.go ~/gocourse/day10/selftest/main.go

После правки — сборка и прогон всего набора: правка могла сломать другой случай.

Пример · только посмотреть, набирать не нужно
stagiaire@lab:~/gocourse/day10/selftest$ go build -o selftest .
stagiaire@lab:~/gocourse/day10/selftest$ for f in cases/*.txt; do echo "== $f"; ./selftest < $f; echo "код: $?"; done
== cases/empty.txt
Посылок нет
код: 0
== cases/junk.txt
ошибка в строке 2
код: 1
== cases/normal.txt
Посылок: 3
Общий вес: 4500 г
Средний вес: 1500 г
Самая тяжёлая: 2500 г
код: 0
== cases/one.txt
Посылок: 1
Общий вес: 700 г
Средний вес: 700 г
Самая тяжёлая: 700 г
код: 0

Команду for заново не набирают: стрелка вверх достаёт её из истории. На контрольной порядок тот же для каждой задачи с вводом: условие → набор вводов с ожидаемым → код → прогон набора → course check 10.


Что может пойти не так

Что видите Что это значит Что делать
bash: printf: -5: invalid option и printf: usage: …, файл ввода пустой строка начинается с минуса, и printf принял её за ключ printf -- '-5\n' > cases/minus.txt: два минуса отделяют ключи от текста
bash: cases/nomal.txt: No such file or directory и код: 1 опечатка в имени; программа не запускалась, код 1 — от bash ls cases, скопировать имя
== case/*.txt, потом bash: case/*.txt: No such file or directory под шаблон не подошёл ни один файл, bash оставил звёздочку проверить папку и расширение
bash: ./selftest: No such file or directory и код: 127 программа не собрана или названа иначе go build -o selftest .
после правки результат прежний ./selftest — старый собранный файл go build после каждой правки
программа не завершается пропущено < $f: программа ждёт клавиатуру Ctrl+C, проверить команду
в каждом случае exit status 1 прогон через go run запускать собранный файл

Дальше: памятка блока 2 — справочник по конструкциям, ошибкам и дефектам — course next