Проверить программу с вводом самому
Сегодня — контрольная точка блока 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.txtprintf '' не печатает ничего, и > создаёт пустой файл.
Ожидаемое — до запуска. Для каждого файла запишите, что должно получиться и с каким кодом. Если сначала запустить, любой вывод кажется правдоподобным. Готовая таблица есть, но она стоит после блока ниже: сначала своя запись по 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 cases2. Программу пока не запускайте: по
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.txt2. Папка
cases, в ней не меньше четырёх файлов.txt: обычный, граница, пустой, мусор.3. Сборка и прогон командой
for:▶ Выполнитеgo build -o selftest . for f in cases/*.txt; do echo "== $f"; ./selftest < $f; echo "код: $?"; done4. Для одного файла — ожидаемое в
.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.want5. Почините программу и прогоните набор ещё раз: снова
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 — строка, на которой программа упала, а текст рядом — чем именно. Начинайте оттуда и дальше три вопроса по порядку:
- Какое значение в этой строке оказалось не таким, как вы ждали?
- Почему именно на этом вводе — что к этой строке успело выполниться, а что нет? Пройдите программу от первой строки на том же вводе.
- Значит, эта строка стоит не там, где нужно, или не хватает проверки? Таблица «Собирается, но считает не то» на странице 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