Skip to content

Prerelease candiate for v1.0.0A8 - #14

Closed
Static-Codes wants to merge 858 commits into
stablefrom
canary
Closed

Static-Codes wants to merge 858 commits into
stablefrom
canary

Conversation

@Static-Codes

Copy link
Copy Markdown
Owner

This build needs significant testing before release, namely around the NFD support (on osx-arm64).

For more information see this extremely detailed changelog: https://github.com/Static-Codes/BrowserAutomationMaster/blob/815f557d65a871bb6e6894d76aa3b3a60e46f107/sections/changelog-for-next.md

Eventually, the distro detection logic should be reworked to use WhichDistroSharp.

That said, it is currently 2:30AM and I need sleep.

…ompilation status for Debian/Fedora/Standalone
…anagers.AppManager.OS.Linux.Functions.GetApps()
Documented future plans to rewrite BAMM to be less verbose and easier to maintain. This new rewrite will up the major version to 2.
Documented future release plans (following a rewrite)
Updated license to GPLv3 and added license notice to add source files
add: gui instructions to quickstart guide in readme
* Fixed validation bug for commands with two quoted args

* Readded a crucial debug function in the DSL parser.
This PR addressed #10, which laid out the requirements for these unit test implementations.
BAMM has been licensed under GPLv3 for over 3 months, however, there have been no releases in roughly 8 months, so this was missed in the process. 

Commit: ec205c3
Brings the GPLv3 relicense, the parser validation fix (#11), and the xUnit
test suite (#12, #13) onto canary.

Conflict resolution: 43 content conflicts resolved in favor of canary's
Core/ namespace layout, carrying over stable's GPLv3 headers. The src/Tests
native-AOT harness and 11 superseded Managers/ files are removed in favor of
their Core/ replacements. Solution file repointed from Tests/ to
BrowserAutomationMaster.Tests/.

Known issue: 8 of the 59 newly added tests fail. Verified identical on a
pristine stable worktree, so the merge introduces none of them.

- FeatureTests (5): tests v1 syntax (`--disable-ssl`); the parser expects
  `feature "disable-ssl"`. The default switch arm correctly returns false.
- JavaScriptBlockTests (1): start-javascript/end-javascript are handled in
  the file-level loop (Parser.cs:655), not in HandleLineValidation, so
  calling it directly cannot return true.
- ClickTests (1): the XPATH case has spaces inside the quotes, so
  line.Split(" ") yields 3 parts, failing the lineArgs.Length != 2 check
  at LineValidation.cs:140.
- KnownBugTests (1): a known-bug guard asserting add-header throws in a
  headless environment; it now returns a validation false instead. No
  positive assertion, so it cannot distinguish fixed from unreachable.
  Introduced in a4dc873. Safe to delete.
The suite had 8 failing tests once the merge landed. None of them were
caused by the merge itself, but all of them had to be sorted out before
the suite could be pushed. In the process a bug in add-header
validation was discovered and rectified.

The XUnit suite came across from stable in the previous merge with 8 tests
already failing. None of those failures were down to the merge, but they
had to be cleared up before the suite could be relied on. Three separate
causes turned up, and all three were already sitting on stable.

1. Platform detection was never getting initialized (21 failures)

   Every test that reached DirectoryManager or Parser threw
   PlatformNotSupportedException. What tripped it up was ordering.
   ProgramFunctions.InitializeAsync calls PlatformManager.SetPlatform,
   which sets the PlatformInfo flags that GetAppDataDirectory branches on.
   The tests call Parser directly, so that call never happened, the flags
   stayed false, and the method dropped through to its throw.

   PlatformInitializer.cs is a new module initializer that makes the same
   call once before any test in the assembly runs. A module initializer was
   chosen over an xUnit IAssemblyFixture because the existing tests take no
   fixtures, so each one would have needed to opt in. That took the count
   from 21 failures down to 8.

2. Tests were asserting things the parser never did (7 failures)

   These were bugs in the tests themselves, and in each case a test had
   been written against an assumption that never held.

   FeatureTests asserted the v1 syntax (--disable-ssl) and expected true.
   The parser has only ever accepted feature "disable-ssl". Searching the
   whole project history turns up no sign of the v1 form. These were
   converted to v2 and the file grew from 6 cases to 11, including
   --disable-ssl as an explicit negative so the removal stays a deliberate
   decision rather than an accident. The feature grammar in 574dca9 and
   the forms already in use in IntegrationTests were both used as the
   reference.

   Three test files called HandleLineValidation(line, "test.bamc", 1)
   against a signature of (fileName, line, lineNumber). They were
   validating the literal string "test.bamc" instead of their intended
   input. The argument order was corrected in FeatureTests,
   JavaScriptBlockTests and KnownBugTests.

   ClickTests asserted that click "//button[contains(text(), 'Submit')]"
   is valid. click splits on a bare space, so a selector can never contain
   one, and spaced selectors are only reachable through click-exp. The
   case had been misfiled. Coverage moved to ClickExpTests, plus a new
   case asserting that click rejects spaced selectors.

3. KnownBugTests was tracking a bug that does not exist

   The test claimed add-header throws IOException or
   ArgumentOutOfRangeException out of Console.WindowWidth in headless CI.
   Two checks say this never happened.

   For a start, Console.WindowWidth returns 0 when no tty is attached. It
   does not throw, verified directly.

   On top of that, the test called HandleLineValidation with its arguments
   swapped, so it was validating "test.bamc" and never reached add-header
   at all. It failed for an ordinary reason, and the headless crash reads
   like an explanation reached for afterwards.

   The test also could not tell "crash" from "fixed", since it was willing
   to accept either outcome. It was deleted along with the KnownBugs
   directory, which held nothing else.

   The same story had made it into the test README, sitting next to a
   /KnownIssues path that does not exist, /FormatValidators in place of
   /FormatValidation, and the v1 feature syntax. All corrected.

   Worth flagging: that test was initially rewritten without first checking
   whether the failure it described could even happen. A grep turned up
   Console.WindowWidth at ANSI.cs:263, that was taken as proof, and the
   made-up reasoning then carried straight into the rewrite.

Parser fix: add-header could never validate

   The rewritten add-header tests turned this up. add-header sits in
   lineArgSpecialCases, so its arguments get split on ' "', which eats the
   opening quote on each one:

     'add-header "Authorization" "Bearer token"'.Split(' "')
       -> ['add-header', 'Authorization"', 'Bearer token"']

   AddHeader hands off to ValidateTwoArgCommand, which wants IsArgQuoted to
   find a quote at both ends. The opening quote has already gone by that
   point, so the check failed every single time and add-header turned down
   its own documented syntax. add-header was the only command hit here.
   fill-text, fill-text-exp and set-custom-useragent check EndsWith('"')
   themselves, and add-cookie and click-at-position stay out of the
   special-case list so they split on a bare space and keep both quotes.

   This came from 6eb460e back on 2026-01-17, "Added new action command
   add-cookie". That commit pushed add-header onto the shared helper, and
   the inline check it replaced was EndsWith-only, so the command worked
   right up until the delegation changed. The pre-refactor code in
   Parser.cs is fine, and 6eb460e only swapped the call.

   There is a second layer too. 4eff309 (#11) added the exact parameter
   needed to put this right, documenting it as "If the argument had a
   leading double quote removed prior to validation". No caller ever
   passed it. The merge in 8d4c52a then settled that hunk in canary's
   favor and dropped the parameter, leaving canary with none of #11's fix.
   It was restored, and AddHeader now passes stripped: true.

   That fix went into the previous commit (22b1355) and is covered by the
   new Commands/AddHeaderTests.cs, which includes a case that reaches the
   quote check rather than the argument count, so the validation cannot be
   quietly loosened later.

Verification

   73 passing, 0 failing, and the solution builds clean with no warnings.

   That second one passed on the first attempt, which was worth catching.
   Every case in AddHeader_RejectsMalformedInput was getting turned away by
   the argument-count check before it ever reached the quote logic, so they
   were asserting nothing at all. The case
   add-header "Authorization" "token" extra was added, which gives three
   arguments with trailing content on the second one, so it lands on the
   quote check where it matters.

Still open

   A selector with nested quotes, //button[contains(text(), 'Submit')],
   cannot be written with either command. click breaks on the space and
   click-exp breaks on the inner quotes, and neither one supports
   escaping. Noted as a known limitation in ClickExpTests.

   feature "add-extension" with a trailing space gets accepted, since the
   second argument of a non-proxy feature never gets looked at. Noted in
   FeatureTests.

   AppLicenseType in src/Publisher/Build/BuildInfo.cs still reads "MIT",
   so the Arch and Debian packages will declare MIT while the project
   itself is GPLv3. That is already present on stable and was not
   introduced here, though it does ship in the same stretch as the
   relicense.
Every existing test calls a function directly. Commands, Features,
FormatValidation, Selectors and Integration all pass strings to Parser or its
helpers and assert on the boolean that comes back. None of them ever hands a
.bamc file to the compiler, which is the only part of BAMM a user runs end to
end.

The new ScriptExecution suite does. It enumerates the .bamc scripts it can
find, runs the real CLI as a child process with compile <file>, and asserts on
the exit code. On its first run it reported 8 defects, and 2 of the 11 example
scripts in the repository had never compiled.

4 of the 8 share 1 cause: the compiler re-implements parsing instead of
calling Parser.IsValidFile, so the 2 implementations have drifted apart. The
parser enforces a feature allowlist, command arity and quote rules that the
compiler never consults, and a script the parser rejects still compiles.

The worst of those 4 is silent. Give the compiler a command it does not
recognise and it emits nothing for that line and exits zero. A script that
quietly drops a step is more dangerous than one that refuses to run, because
the run looks successful.

The fix could not live in the emit switch. Most of its cases are written case
"click" when Click(...) is false, so the case matches only when the command
fails. A command that succeeds matches nothing and falls out of the switch,
and a command that was never recognised does exactly the same thing. The
compiler cannot distinguish the 2 from inside that switch, so the check has to
happen before it. Adding a default arm instead broke all 11 examples, which is
what exposed the problem.

The remaining 3 in that group are smaller. Parser.IsValidFile set
featureBlockFinished from the first non-comment line it saw, so the first
feature line closed the feature block and a second one was reported as
misplaced; a script could declare exactly 1 feature. GetDesiredUrls splits raw
lines on a space and requires exactly 2 tokens, so a visit command carrying a
trailing comment was not collected and the script came back reporting no visit
commands at all. DeleteCommentIfPresent searches for " // ", so a comment
consisting of 2 slashes and nothing else was never removed.

The example scripts were part of the same problem. no-ssl-example.bamc is the
only script in the repository containing a feature command, so
Parser.featureArgs had no example exercising it, and that script used feature
"no-ssl", which is not in the allowlist, with both feature lines placed after
the visit. Fixing the compiler alone would have left the sole example for the
feature block still broken.

2 of the 8 are outside the compiler.

GetTerminalBackgroundColor is the one that escapes this repository. It runs
printf with an OSC 11 escape to /dev/tty and reads the reply with read from
/dev/tty. /dev/tty is the controlling terminal, the same one the invoking
shell is reading from, so any BAMM run with a redirected stdin left the
terminal response in the shell input queue, where the next command typed at
that terminal consumed it. The same code path also emitted an exception on
every such run, because ForegroundMatch parses a single rgb group and the
queued responses concatenate.

StartGUIThread has a default parameter of DEFAULT_PORT, and the call site that
parses --gui --port==X passed no argument at all, so the listener bound 8008
regardless of the port the user passed. The line above it still carries a
commented-out StartServer(port), which dates the regression. Out-of-range
values now get a message instead of reaching HttpListener.

The GUI server answered an unrecognised route by logging a warning and falling
out of the switch without writing a response, so the socket stayed open with
nothing sent and the browser saw a network error with no status code. It now
returns 404 with the reason in the body.

Also added, not fixed: a /gui_version endpoint reporting the version of the
GUI this build embeds. The GUI moved to its own repository and its own release
cadence, so the version is read back out of the embedded archive rather than
from a constant that could drift from the archive.

Making the suite usable needed 2 changes. BAMM writes through Spectre.Console
to the process-global Console from inside its validation routines, and xunit
runs 1 collection per test class in parallel, so the output interleaved
character by character. xunit.runner.json disables collection parallelism.
ConsoleCapture additionally redirects that output into a buffer, because the
inverse tests print every rejection they provoke, which left a passing run
looking like a wall of errors.

Verification: 110 passing, 0 failing, 1 skipped. 10 of the 11 examples
compile; marketplace.bamc does not, because facebook.com returns HTTP 400 to
the Firefox User-Agent that RequestManager sends and 200 to the curl default,
so the compiler and the pre-flight probe agree the URL is unreachable. The GUI
server was exercised over HTTP rather than mocked: /gui_version returns the
expected version on the default port and on a requested one, an unknown route
returns 404, and /load still returns 200.

Left undone deliberately. Transpiler.validCommands is hand-maintained because
the switch cannot detect unknown commands, and CompilationValidationTests
asserts it matches Parser.HandleLineValidation exactly so the 2 cannot drift
apart silently; removing the duplicated parsing entirely is a larger change
than this. A selector containing its own quotes remains unwritable through
either click or click-exp, as ClickExpTests already records.

sections/changelog-for-next.md is updated throughout, including the provenance
of 2 unrelated cleanups that were already in the working tree when this
changeset was assembled.
BAMM identified its host distribution with a hand rolled /etc/os-release
parser and a hand maintained package manager table, where each of the 15
entries in Distros.cs restated the same handful of values. That table had
drifted. PCLinuxOS reported an apt package manager but was published as an
rpm, and Oracle Linux never matched because the real ID is ol and not
oracle.

WhichDistroSharp 1.0.1 owns both of those now. Distros.cs keeps only what
is unique to BAMM, and each entry names the upstream distros it claims
rather than a single ID. Two types collided on import, so the upstream
enum is aliased to WhichDistro and the unambiguous names moved into
GlobalUsings.cs.

Detection resolves a dictro in O(1) from a dictro to entry table, and the
93 distros BAMM cannot identify are an explicit and tested contract rather
than a silent prompt on every run.

The Linux installer reads a generated package format map covering 110
distros in place of 12 hardcoded ones, and falls back to those when the
fetch fails so an offline host still installs. Publishing the map is a
build target, since a test cannot both assert against a snapshot and
rewrite it.

Nine defects were fixed along the way, and the two that changed user
visible behaviour were these.

1. Raspbian, openSUSE Leap and openSUSE Tumbleweed were never matched

   Their real ID values matched no entry, so those hosts were asked to
   pick a base distribution on every single run. install.sh has always
   treated Raspbian as Debian family, so the installer and the
   application disagreed about it.

2. The Linux installer's error guards did nothing

   show_error_and_exit is called three times near the top of install.sh
   and defined near the bottom. Bash resolves a function when it is
   called and not when the file is read, so every one of those calls
   reported command not found and the script carried on. A macOS user
   was told nothing and the script went on to install Linux packages.

3. The installer could install a package left over from a failed run

   wget derives the file name from the URL and saves to <name>.1 when
   the target already exists. The temporary directory is only removed on
   success, so the next run installed the stale package instead of the
   new release. The download now uses wget -O, which forces the name.

4. RefreshDebianAptCache never ran

   It compared the Distro object against a DistroBase value, so it was
   always false and apt-get update never executed, including on the
   packaging paths that call it because a dependency is missing. The
   rename alone would not have fixed it, since object.Equals accepts
   anything and returns false across types.

5. Python 3.13 and 3.14 could not be selected

   HandlePythonVersionSelection filled a fixed string[6] against a
   mapping of eight. Installing six interpreters dropped the last two.

6. CI tested a different repository than the one under review

   dotnet.yml checked out the triggering commit, then cloned the
   default branch and built that instead. The trigger also
   named main, which is not a branch in this repository, and GitHub
   resolves a missing ref to whatever it last pointed at, so the
   workflow had not run since January.

7. install.sh did not recognise Oracle Linux

   The ID extraction was also unanchored, so ID_LIKE could match ahead
   of ID, and it left the quotes in ID=ubuntu behind.

8. Terminal probe tests failed only on a workstation

   The suite asserted its own precondition with Assert.True, which holds
   under CI and is false for anyone running dotnet test from an
   interactive terminal. It now skips.

9. Unsupported distribution messages pointed at the wrong problem

   A distro BAMM ships no package for was reported as not currently
   supported, which is not something a user can act on.

Coverage went with it. DistroMappingTests pins every forwarded value to
the literal that was in Distros.cs before the move, so a refactor that
changes what BAMM executes fails the suite rather than on a machine.
test-install.sh covers install.sh, which is curl | bash and cannot be
reached by xunit at all, and asserts the helpers are defined before
they are first used.

The 93 unclaimed distros and the 4 published package formats are recorded
in sections/known-distribution-gaps.md, which is linked from the README.
@Static-Codes

Copy link
Copy Markdown
Owner Author

After a couple days of trial and error, I was finally able to replace BAMM's fragile distro and package resolution logic using a combination of WhichDistroSharp, source generation, and an MSBuild target that generates and ships an updated test file. The aforementioned test file properly identifies blindspots in distro coverage, allowing for both end-users and developers to stay updated on BAMM's linux support!

Commit: 40b0af8

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant