/test/testit05.php looks like what it was: a numbered PHP test script on a public weather site. Historical PWS hosts often left test/, phpinfo(), and “dump the array” pages world-readable so the operator could debug from work. That habit is a security smell and a scientific smell at the same time. A parser that can only be validated by hitting a live URL is a parser you cannot regress when Weather Display changes climatedataout.html.
This article is the missing harness: golden files, fixtures built from ClientRaw and climate HTML, and how to run those checks offline. It is not a live debugger and does not execute test PHP.
Why a public /test/ URL is the wrong kind of observability
A test endpoint on the station host typically did one or more of the following:
- read the live
clientraw.txtand print every field; - parse climate HTML and
var_dumpthe array; - echo PHP version, include paths, or working directories;
- accept a query parameter for which file to open.
Each of those is useful in development. On a public URL they leak station coordinates, indoor temperatures, filesystem layout, and sometimes credentials that were sitting in a sibling config file. They also invite people to hammer GD and disk the same way hotlinked weather banners did.
The scientific failure is subtler. If “tests passing” means “the URL still returns 200,” you will not notice that July rain moved to column 8. Layout drift looks healthy until a human compares two winters.
Move validation to a process you run on purpose: a CLI script, a CI job, or a local PHPUnit (or plain PHP) run against files that are not web-accessible. The scripts index listed WD extractors that died after format changes; those deaths should have been test failures, not forum reports.
Golden-file tests
A golden file is a stored expected output. For a weather parser:
- Keep a fixture input: a sanitized copy of
clientraw.txt,climatedatayearout.html,climatedataout.html, or average/extreme HTML. - Run the parser in a function that returns a structure, not HTML.
- Compare that structure to a stored JSON (or PHP array) golden result.
- If WD changes the input layout, the comparison fails. You then update the parser, or you update the golden file with a reviewed, intentional change.
The golden file is the contract. Diff it in version control. Do not regenerate it from live FTP on every test run; that would bless whatever WD uploaded today, including a truncated file.
Sanitize fixtures before they leave the station computer. Replace real lat/lon with a documented dummy pair, strip comments that contain hostnames, and keep units and the layout quirks you actually need. A fixture is not a climate publication. The April 2009 average/extreme sample shows why a full public dump is the wrong artifact; a trimmed, labeled fixture is the right one.
What to put in a ClientRaw fixture
Weather Display’s clientraw.txt is a delimited live file that community templates already treat as the AJAX source (Saratoga WD setup; Weather Display). A fixture should include:
- a realistic field count for the WD version you support;
- at least one missing or dashed field if your parser claims to handle gaps;
- a timestamp field you can assert;
- units implied by the rest of your site (do not mix a metric ClientRaw with imperial goldens).
Assert field indexes by name in your code (a map from index to temp_c, wind_kt, …) and assert a few values. Do not snapshot the entire concatenated string as one blob unless you also have field-level checks; a single extra comma then fails without telling you which observation moved.
Related ClientRaw parsers on this host follow the same rule: parse a file, return a structure, compare to goldens.
Climate HTML fixtures
Weather Display emitted at least two climate HTML files (year summaries and daily-within-month layouts such as climate2). Your harness should keep one fixture per filename and WD layout generation. Name them so the generation is visible, for example climatedataout-wd-english-dashes.html, not sample.html.
Assert:
- header labels you bind to (so a renamed “Max Temp” fails);
- month or day cardinality;
- mapping of unused cells to missing, not zero;
- one trace token if the fixture contains
T; - unit strings.
When two parsers disagree on production data, add both HTML files from the same upload batch to the harness and assert the disagreement protocol (units, water year, trace) rather than asserting that the two arrays are equal. They should not be equal if they measure different aggregations.
Regression when WD output changes
Vendor HTML is an unofficial API. Plan for breakage:
- Pin tests to fixtures, not to the live FTP directory.
- When you upgrade Weather Display, capture new HTML once, run the suite, and expect failures.
- If the new layout is intentional, update goldens in the same commit as the parser change so the diff explains the new contract.
- Keep the old fixture until you drop support for that WD generation. Operators do not upgrade in lockstep.
A parser without a pinned WD generation in its README is unsupportable. The historical TNET notes that climate1 and the average/extreme extractor became non-operational after format changes are the postmortem. Golden files are the pre-mortem.
How to validate without a live debug endpoint
Patterns that stay off the public web:
- CLI:
php bin/assert-climate.php tests/fixtures/climatedataout.htmlexits nonzero on mismatch. - Local only: a
/test/directory that the web server denies except on127.0.0.1, still inferior to CLI because it trains the team to debug in the browser. - CI: run the same assertions on every commit. No station secrets in the repo; fixtures are synthetic or sanitized.
- Operator smoke check: a private status page that shows “last parse OK, file age 4 min” without dumping fields.
If you need to inspect a live parse, write the structure to a log with rotation and access control, or run the CLI against a downloaded copy. Do not add ?pretty=1 to a public script.
Tiny original assertion shape (conceptual, not a recovered TNET script):
$result = parse_climate_year($html);
assert($result['months'][1]['rain'] !== 0.0 || $result['months'][1]['rain_state'] !== 'missing');
assert($result['units']['rain'] === 'in');
The first line is the actual rule: zero and missing must not share a representation.
Security minimums for leftover test PHP
If you inherit a station site:
- Delete or block
/test/,/info.php, and anyvar_dumpwrapper. - Disable
display_errorson the public vhost. - Do not accept file paths from the query string.
- Treat old test scripts as unlicensed and unsafe to rehost; TNET does not.
The legacy scripts hub is the catalog of explainers that replaced those endpoints. Do not rehost leftover test PHP.
TNET’s research side has the same architectural split: quality control happens against documented sources, not against an anonymous public dump. How those sources are handled is public; internal scoring is not. For the source-and-QC half, read data sources, quality controls, and methodology. This /test/ URL stays a warning label: validate parsers with fixtures, not with a world-readable script.