Repository navigation
Quality
What has to be true before anything is allowed into this library. Each of these is checked by a machine — a rule nobody can fail is not a rule, it is a preference.
Every change starts with a test that fails. You write it, run it, watch it fail for the reason you expect, and only then write the code that makes it pass.
The order matters. A test written afterwards shows that the code does what it does. A test written first shows that the code does what was asked.
A bug starts the same way: a test that reproduces it. If you cannot reproduce it in a test, you do not yet know what the bug is.
The build treats every warning as an error. There is no warning list to read afterwards and nothing to forget to look at — a warning stops the build, because a warning is a failure that has not happened yet.
That includes documentation. Every public part of the library carries a comment explaining it, because the things you can call are the product. Tests are the exception: a test's name already says what it checks, and repeating it in a comment adds nothing.
At least 90% of the lines and 90% of the branches have to be reached by the tests — for every class, not only for the library as a whole. Below either figure the build fails and says which classes are short.
Measuring only the total is how an untested class hides: a hundred well-covered ones carry it over the line, and nobody notices until the day it is edited.
./tools/coverage/run.ps1 -Check
It runs every suite it finds — there are six: the library's; the libtorch engine's, which carries libtorch, as the
library's never does; the ML.NET learner's, which carries ML.NET and the native side it comes with; and
the notebook's, the host's and the server's, which run on Verso's own engine, which needs
another version of the C# compiler than the rest — and judges each class on everything that reached it, pooling the
suites' reports itself: the coverage tool's own merge of the same reports came out differently from one run to the
next.
Every suite runs on .NET 8 and on .NET 10, because the packages ship for both: the coverage is measured on
.NET 10, and a test that fails on .NET 8 fails the check just the same. The page deepsharp-serve draws is tested in
a real browser against the real server, a test for each promise it makes; its script is not counted in the coverage,
which stays the C#'s.
One thing worth knowing. The tests are a program that runs itself. The usual dotnet test command starts
a tool that looks for tests to run, finds none, and finishes happily — on the first commit here it reported
0% coverage over 0 tests. That looked like a failing check, but it was really a check measuring nothing, and
the same setup elsewhere would look like a passing one. So the check runs the test program and measures it
while it runs.
If a branch is hard to reach with a test, that is usually the code telling you it should not be there. Delete it rather than write a test that pretends to cover it.
A network that trains is no evidence that it trains right, so the arithmetic is held to the libraries whose numbers people already believe. Every layer's forward pass and its gradients, every loss and every optimizer step are matched against PyTorch on the same rows; early stopping against Keras's own rule; a window padded as 'same' and the normalisations in Keras's words against TensorFlow and Keras; every measure of amounts and classes a report takes against scikit-learn, to a millionth of a millionth, and the three that compare shares against scipy and xskillscore; the correlation against numpy and scipy; and the random draws against NumPy's Philox. Every gradient rule is also checked by nudging its inputs a little either way and watching the loss. A run resumed from a checkpoint is held to the run that never stopped, bit for bit on the engine it was taken on; a network through Keras's words to the same network written as code; and a network a pipeline declares to the same one written beside that pipeline and fitted by hand.
Every engine is held to one contract, written once against what the library publishes and run on each: the light engine, an engine of the tests' own on memory allocated outside .NET, and libtorch on the processor and on a card — each total within three roundings of the size of its terms, per operation and per training step. A model read from another framework's file is held to what that framework answered for it: the Titanic network PyTorch trained, and the one Keras trained, answer the 135 test passengers within five roundings of the chances their own framework gave, from every file and graph they were saved as. The files and the scripts that made them are kept with the tests.
What a person runs is the package, not the build every suite ran, and a package can lack what the build had while every test still passes: a file left out, or the build for one runtime finding a library only on the other. So before a package leaves a run of the workflows, five things are started from the packages just made, each in a folder of its own and with a package cache of its own:
-
deepsharp-serve, installed the way a person installs it — its newest build, its build for .NET 8 on .NET 8, and that build on the newest runtime, where it goes once .NET 8 is removed. Each is asked what a browser would ask: its page, refused without its token and for a file beside the notebook, the folder's notebooks, and over a notebook's socket, as its page asks, the notebook, a run of its step, and a file beside it asked for as a notebook, which is refused. When the build for .NET 8 still carried two libraries of its own, a package missing one of them, which no suite can see because the suites run the build, failed here: it served no notebook on .NET 8. -
The notebook package, installed by Verso's own installer and loaded by Verso's own loader, in a host that holds
nothing of DeepSharp, on .NET 8 as
verso serveruns and on the newest runtime as Verso's VS Code extension runs. The files Verso lays out must be the list the notebook's tests hold to what the build resolves; the sample notebook's report block shows the data, its C# cell trains a network on DeepSharp's packages named by their NuGet ids and draws the loss, the report block draws the measures handed back, everything the notebook refers to is found in the folder Verso installed it to, and every method of DeepSharp's compiles against what is there. - An application of the libtorch engine, referencing its package and nothing of DeepSharp's source: brought no libtorch, it is refused, naming every package that brings one; brought the processor's, it fits a step of the networks sample's Titanic pipeline.
- An application of the ML.NET learner's packages, referencing them and nothing of DeepSharp's source, started twice: bringing the package that trains, it fits a tree of the Titanic pipeline and writes the model beside its pipeline as one file; bringing only the package that declares and reads, it reads that file back and carries nothing of ML.NET at all.
- An application of the live source's package, referencing it and nothing of DeepSharp's source, started twice: bringing the package that lands, it lands thirty days of candles from a stand-in venue on this machine — never from Binance — and writes the pipeline that reads the landing; bringing only the pipeline library, it reads that pipeline back with the library's own catalog, runs it over the landing, and carries nothing of the landing's package and nothing of Polly.
dotnet pack DeepSharp.slnx -c Release -o nupkgs
bash tools/serve/check.sh nupkgs
bash tools/verso/check.sh nupkgs
bash tools/torch/check.sh nupkgs
bash tools/ml/check.sh nupkgs
bash tools/live/check.sh nupkgs
And the pack itself checks each package's public surface against the one nuget.org already carries, so a change nobody meant to make to a published member fails the build instead of somebody's upgrade.
The README's example and the code on this wiki are what a person copies first, so tests hold them to the packages as
built. The README's example is compiled and run over a file of the shape it names. Every C# block of the wiki is
compiled, with the usings a console program has, and fails on a warning as the build does — an obsolete form included;
and a block marked as a notebook's cell is run in Verso's own engine, after the blocks above it have handed their
pipeline over. The wiki is cloned beside the repository, as DeepSharp.wiki, where those tests and the build machine
read it.
Some of those programs are run, not only compiled — the README's example and the pipelines of the tutorial — and a run that named the live source would ask Binance on every build of every machine that builds this repository, a guest of a budget that is shared with whatever else runs on the address. So a test holds the programs that are run to never naming it: a page that lands, Live sources, is compiled and never run, and before any test is found the core's suite and the notebook's send every address but this machine's to a proxy nothing listens at.
Pushing a tag makes the release on GitHub, with the changelog's section for that version as its notes, and starts the publish: every suite on both runtimes, on the tagged commit, what a person installs started from its package, and every package pushed to NuGet — or none. A tag that names another version than the one the build declares is refused before anything is packed, because a package on NuGet cannot be taken back.
CodeQL reads the code on every change and once a week — the C#, and the script of the page deepsharp-serve draws — looking for both security holes and sloppiness — a
comparison that is always true, something opened and never closed, a parameter nobody uses. It is set up as a
file in the repository rather than a switch in a settings page, so you can see what it does and it cannot be
turned off quietly. Every finding is fixed, or dismissed with its reason written beside it — exact on purpose, a
boundary around code a part brings, generated by the build — so the list of open findings holds only what is new.
Dependabot watches the packages this repository depends on, weekly, and the build steps monthly. Something that was safe when it was pinned does not stay safe, and nobody spots that by reading build output. One package it leaves alone: the Verso abstractions the notebook package is compiled against. Verso refuses an extension compiled against a newer minor version than the one it runs, so that version decides the oldest Verso the notebook loads in — a decision a test holds, not an update to take.
- No classes called
Helper,UtilorManager. Those are drawers nobody owns. A shared function belongs on the type it works with; a shared idea belongs in an interface. - Returning several values means a small named type, not a tuple. A tuple loses its names everywhere it matters: in autocomplete, in error messages, in the documentation.
- A method or a constructor takes four parameters at most. A fifth says some of them belong together, in a small named type of their own.
- Nothing helps itself to a shared object. What something needs is handed to it.
The version number is decided when the work starts, and the same number then appears in the build, at the top of the changelog and in the README — a test holds the three together. Nothing is published before it has one.
The commit that changes how something behaves also updates what describes it — the README, the design notes, the changelog, the comments on what you touched. A change whose documents lag behind is not finished.