Skip to content
CogniSkill

LESSON 4 OF 4

Prove the collector’s limits and extend it deliberately

Practice notesBooks for further learning

Independent tests with literal expectations

The download includes the script and this Python standard-library test harness. Python is used only to construct awkward filenames and assert external behavior; the collector remains Bash. If you prefer another harness, keep independent fixture expectations and subprocess exit-status checks.

from pathlib import Path
import hashlib
import subprocess
import tempfile
import unittest

SCRIPT = Path(__file__).with_name('inventory.sh')

class InventoryTests(unittest.TestCase):
    def setUp(self):
        self.tmp = tempfile.TemporaryDirectory()
        self.root = Path(self.tmp.name)
        self.source = self.root/'input with spaces'
        self.output = self.root/'out'
        self.source.mkdir(); self.output.mkdir()

    def tearDown(self):
        self.tmp.cleanup()

    def run_report(self, output=None):
        return subprocess.run(['bash',str(SCRIPT),str(self.source),str(output or self.output/'report.tsv')],capture_output=True,text=True)

    def test_literal_sizes_hashes_and_quoted_name(self):
        (self.source/'alpha.txt').write_bytes(b'abc')
        (self.source/'two words.txt').write_bytes(b'hello\n')
        self.assertEqual(self.run_report().returncode,0)
        lines=(self.output/'report.tsv').read_text().splitlines()
        self.assertEqual(lines[0],'filename_bash_quoted\tbytes\tsha256')
        self.assertEqual(lines[1], 'alpha.txt\t3\t'+hashlib.sha256(b'abc').hexdigest())
        self.assertEqual(lines[2], 'two\\ words.txt\t6\t'+hashlib.sha256(b'hello\n').hexdigest())
        self.assertEqual((self.source/'alpha.txt').read_bytes(),b'abc')

    def test_empty_population_not_literal_glob(self):
        self.assertEqual(self.run_report().returncode,0)
        self.assertEqual(len((self.output/'report.tsv').read_text().splitlines()),1)

    def test_existing_report_preserved(self):
        report=self.output/'report.tsv';report.write_text('KEEP\n')
        self.assertEqual(self.run_report().returncode,2)
        self.assertEqual(report.read_text(),'KEEP\n')

    def test_inside_input_refused(self):
        self.assertEqual(self.run_report(self.source/'report.tsv').returncode,2)
        self.assertFalse((self.source/'report.tsv').exists())

    def test_symlink_match_refused_without_report(self):
        (self.source/'alias.txt').symlink_to('/does-not-exist')
        self.assertEqual(self.run_report().returncode,2)
        self.assertFalse((self.output/'report.tsv').exists())
        self.assertEqual(list(self.output.iterdir()),[])

    def test_hidden_and_nested_outside_scope(self):
        (self.source/'.hidden.txt').write_text('secret')
        nested=self.source/'nested';nested.mkdir();(nested/'child.txt').write_text('nested')
        self.assertEqual(self.run_report().returncode,0)
        self.assertEqual(len((self.output/'report.tsv').read_text().splitlines()),1)

    def test_tab_newline_and_leading_dash_names(self):
        for name in ['-n.txt','tab\tname.txt','line\nname.txt']:
            (self.source/name).write_bytes(b'x')
        self.assertEqual(self.run_report().returncode,0)
        lines=(self.output/'report.tsv').read_text().splitlines()
        self.assertEqual(len(lines),4)
        self.assertTrue(all(len(line.split('\t'))==3 for line in lines))
        self.assertTrue(all(line.split('\t')[1]=='1' for line in lines[1:]))

if __name__ == '__main__':
    unittest.main()

Run python -m unittest -v from the downloaded project directory. Seven tests cover the baseline, empty input, no-overwrite behavior, output separation, rejected symlink matches, excluded hidden/nested files, and difficult names. The baseline hashes are calculated from literal fixture bytes, not from the report under test. That independence matters: a test which simply calls the same faulty hash-extraction helper can repeat its mistake.

Make a defect and explain the failing assertion

In a separate copy, remove shopt -s nullglob. The empty-population test should now fail because the literal pattern reaches validation. Restore it. Next change "${files[@]}" to unquoted ${files[@]}. The space-containing filename test should fail. Restore it before experimenting further.

Do not treat mutation as a competition to invent dangerous code. It is a controlled exercise in your fixture directory. The useful result is an explanation: which requirement was violated, which test observed it, and whether other requirements remain covered. Passing these mutations does not prove the script secure against malicious filesystem races or suitable for unattended production work.

Project A — Change scope without hiding the change

Design an optional --include-hidden mode. Write expected results before implementing it: the baseline should gain exactly one row, .hidden.txt, but nested files should remain excluded. Be explicit about whether matching dot names requires dotglob, whether those names contain sensitive material, and how the run's scope is recorded. A hidden-file flag is a population change, not merely a cosmetic preference.

Do not add recursion by reusing a newline-separated find display. If recursion is a future requirement, design a NUL-delimited path interface and a validation policy for links, mount boundaries, permission failures and huge populations. Keep that proposed design separate from the current collector's release claim.

Project B — Add a threshold without changing the report grain

Add a review flag for files with more than five bytes. The baseline has one review file: two words.txt with six bytes. A file containing exactly five bytes is not selected under a strict-greater-than rule. Test 4, 5 and 6 independently, plus an empty file. The threshold must be a validated integer argument, not arbitrary text inserted into arithmetic evaluation.

Retain all source rows, adding a classifier field rather than dropping small files. Distinguish the number of inventory rows from the number of review rows. If an analyst asks for a total size, state that it is a sum of observed byte counts in this population, not disk allocation, compression size or backup-storage consumption.

Project C — Write a handoff another operator can trust

Deliver a runbook describing prerequisites, scope, input ownership, output permissions, no-overwrite behavior, failed-run evidence and cleanup of abandoned temporary files. Name the assumption that inputs are stable. Explain how you would test unreadable files as a normal user; the supplied validation did not establish that access denial under an unprivileged identity.

For an unattended job, define its working directory, environment, approved identity, scheduling overlap policy, bounded runtime and notification owner. A script that works in your interactive terminal does not inherit those controls. Use a new output name per scheduled run and decide who may read hashes and filenames. Even a read-only collector can expose sensitive metadata.

Acceptance and references

Submit the fixture, original and repaired scripts, seven passing tests, at least two caught defects, a threshold extension and a short evidence note. The note must distinguish byte count from character count, no-match from collection failure, excluded path from rejected path, and completed publication from crash durability.

Consult the GNU Bash Reference Manual sections on quoting, filename expansion, arrays, redirections and traps, and GNU coreutils documentation for wc, mktemp and ln. Those explain the language and utility interfaces; the synthetic case and acceptance requirements here are original course exercises. No paid book is needed to finish this standalone project.

Official references: Bash Reference Manual and GNU coreutils manual.

Transfer project — changed conditions

Implement the fixture with spaces, wildcard characters, leading hyphens and, if supported by your test, a newline in a filename. Verify file contents and count after copying. Inject an unwritable destination and show a failure status without deleting sources. State your Bash/utility assumptions and avoid broad recursive cleanup commands. A safe test harness should use a known disposable directory with an explicit path.

Practical assessment rubric

Score each dimension 0–2: 0 = absent or materially incorrect; 1 = partly correct with an unresolved gap; 2 = correct, explicit and supported by the supplied evidence or an actual labeled lab result.

  1. Evidence/model: identify the relevant entities, inputs, assumptions and boundaries without inventing facts.
  2. Main result: derive the case-specific expected result, calculation or decision and show the intermediate reasoning.
  3. Failure/boundary: test the changed condition and explain why the original conclusion does or does not transfer.
  4. Verification: supply reproducible steps and expected/observed results; label manual traces as manual, not executed.
  5. Communication/safety: explain limitations, owners and a proportionate next action without unsafe or unnecessary access.

Target: at least 8/10, with no zero in main result or communication/safety. This is a local learning rubric, not an accredited certification standard. A written/manual solution demonstrates reasoning only; operational competence additionally requires the relevant executable lab in an authorized environment. The native multiple-choice score is separate and does not automatically grade this rubric.

Submission template

  • My input/evidence and assumptions:
  • Expected result with calculations or trace:
  • My implementation/decision and justification:
  • Changed-condition result:
  • Observed execution, or explicitly labeled manual trace:
  • Failure and recovery evidence:
  • Unverified limitation and next action:
  • Self-score with evidence for each dimension:

Remediation

If the main result is wrong, reconstruct the supplied case from the raw inputs before changing code or controls. If the boundary case is wrong, identify the assumption that changed and rerun both the original and changed case. If verification is missing, do not convert a prediction into an observed result. Reattempt the transfer task without copying the worked solution, then explain it to another person.

Official reference for further study

Assessment: explain your decisions

Choose an answer and write your reason before opening each explanation. Then explain why the other choices do not fit the evidence. Use missed questions to revisit the lesson. This knowledge check is only one part of assessment: complete the practical transfer project as well. It does not predict certification-exam success.

1. Which preserves a name with spaces as one argument?

  1. "$name"
  2. An unquoted expansion everywhere
  3. Deleting spaces
Answer and explanation

"$name" — Quoting preserves the intended argument.

2. Why use a fixed printf format?

  1. To change file ownership
  2. To keep input as data rather than format instructions
  3. To grant root access
Answer and explanation

To keep input as data rather than format instructions — Data should not define the format string.

3. What should a missing input file produce?

  1. A normal zero-row success
  2. A silent deletion
  3. An explicit failure
Answer and explanation

An explicit failure — Missing input is not the same as an empty valid input.

4. The input directory contains spaces. Which expansion passes its path as one argument while allowing a .txt glob?

  1. "$input"/*.txt
  2. "$input/*.txt"
  3. $input/*.txt
Answer and explanation

"$input"/*.txt — Quote the variable expansion to preserve the directory argument, leaving the glob outside those quotes. Fully quoting the pattern makes the asterisk literal; leaving the variable unquoted can split its value.

5. Another process creates the output after the preflight existence check. What protects its contents?

  1. The earlier check permanently reserves the path.
  2. The final exclusive publication operation refuses the existing target.
  3. The collector may overwrite it because the check previously passed.
Answer and explanation

The final exclusive publication operation refuses the existing target. — Preflight checks can race. The final GNU ln -T operation provides the no-overwrite boundary. Publication failure must remain a failure rather than falling back to an overwriting copy.

6. The report has zero rows because only hidden and nested files exist. What does that establish?

  1. The source machine contains no text files.
  2. Every file was unreadable.
  3. No files were selected within the declared visible top-level scope.
Answer and explanation

No files were selected within the declared visible top-level scope. — The population contract excludes hidden and nested files. A header-only report makes a narrow observation, not a machine-wide inventory or a guarantee that the chosen directory is authoritative.

7. Why is unquoted $f unsafe for daily report.txt?

  1. Word splitting can turn one filename into multiple arguments.
  2. Spaces are impossible in filenames.
  3. It automatically encrypts the file.
Answer and explanation

Word splitting can turn one filename into multiple arguments. — The shell’s expansion rules change argument boundaries unless the value is quoted appropriately.

8. What problem can -summary.txt cause?

  1. It always expands into every file.
  2. A utility may parse it as an option rather than an operand.
  3. It cannot exist on a filesystem.
Answer and explanation

A utility may parse it as an option rather than an operand. — Leading-hyphen operands require deliberate option-boundary handling supported by the utility.

9. What demonstrates the copy workflow handled a failure correctly?

  1. It prints success regardless of command status.
  2. It removes the source to simplify retry.
  3. The source remains intact and the run reports the failed copy with a failure status.
Answer and explanation

The source remains intact and the run reports the failed copy with a failure status. — Failure handling should preserve original data and expose incomplete work. A reassuring message is not verification.

Open the scored course assessment to use the existing practice-test engine. Earlier attempts remain part of your history.

YOUR NEXT STEP

Keep building your skills

No directly related CogniSkill book is mapped to this course yet. Continue with the free learning, or compare the publishing catalogue by your next goal.

Explore books by topic and goal →

Sign in to save lesson completion. Reading position is separate from completion.

Practice notes — your working space

Save predictions, outputs and explanations as you work. Notes stay on this browser, not your account; people sharing this browser can access them. Avoid passwords and sensitive data. Export to keep a portable copy, or clear your notes before leaving a shared device.

How was this lesson?

Help us improve this lesson.