starting new job...
→ running command: dir='/etc/byggsteg/job-clone//wikimusic/trunk'
branch='trunk'
if ! command -v git >/dev/null 2>&1; then
echo 'byggsteg: git is not on this deployment PATH, so the clone cannot be updated' >&2
exit 1
fi
if ! cd "$dir"; then
echo "byggsteg: cannot enter the clone $dir" >&2
exit 1
fi
root=$(git rev-parse --show-toplevel 2>/dev/null)
if [ "$root" != "$(pwd -P)" ]; then
echo "byggsteg: $dir is not a git checkout; remove it and the next run clones it again" >&2
exit 1
fi
if ! origin=$(git remote get-url origin 2>/dev/null); then
echo "byggsteg: $dir has no origin remote to fetch $branch from" >&2
exit 1
fi
before=$(git rev-parse --short HEAD 2>/dev/null) || before=none
attempt=1
while :; do
if GIT_TERMINAL_PROMPT=0 git fetch --quiet origin "+refs/heads/$branch:refs/remotes/origin/$branch"; then
break
fi
if [ "$attempt" -ge 3 ]; then
echo "byggsteg: could not fetch $branch from $origin after 3 attempts; is the branch still there, and is the remote reachable?" >&2
exit 1
fi
echo "byggsteg: fetching $branch from $origin failed; retrying in $((attempt * 2))s" >&2
sleep $((attempt * 2))
attempt=$((attempt + 1))
done
if ! git checkout --quiet --force -B "$branch" FETCH_HEAD; then
echo "byggsteg: fetched $branch from $origin but could not check it out" >&2
exit 1
fi
after=$(git rev-parse --short HEAD)
if [ "$before" = "$after" ]; then
echo "byggsteg: $branch was already at $after"
else
echo "byggsteg: $branch moved from $before to $after"
fi
byggsteg: trunk moved from 4a5fa2f to 660f50b
→ running command: cd /etc/byggsteg/job-clone/wikimusic/trunk && uname -a
Linux wolk-jjba 7.2.11-gnu #1 SMP PREEMPT_DYNAMIC 1 x86_64 GNU/Linux
→ running command: cd /etc/byggsteg/job-clone/wikimusic/trunk && git log --name-status HEAD^..HEAD
commit 660f50b0a208c799cf59b0fa4b95c692a1870fdb
Author: Josep Bigorra <jjbigorra@gmail.com>
Date: Mon Nov 2 23:56:55 2026 +0100
feat: Improve font configuration
M .github/workflows/ci.yml
→ running command: cd /etc/byggsteg/job-clone/wikimusic/trunk && maak test
scanning directories for unit tests...
scanning present working directory for unit tests...
shuffled the files with seed 935548772 (replay with VERITAS_SEED=935548772)
found unit tests: (guitarpro-view-spec.scm error-pages-spec.scm config-spec.scm views-spec.scm auth-spec.scm about-spec.scm domain-spec.scm assets-spec.scm controller-spec.scm sql-spec.scm guitarpro-spec.scm)
running unit tests from file: guitarpro-view-spec.scm, entrypoint: spec
ℹ️ info - shuffled with seed 935548772 (replay with VERITAS_SEED=935548772)
+----------------------------------------------+
[📑 suite] What a tab's own header is shown as
[🧪 test] a key is listed by the name of the key and not the count
[⚙️ assert] two flats is B flat
✅ got: Bb
[🧪 test] the numbers a header always carries are always listed
[⚙️ assert] the measures
✅ got: 42
[⚙️ assert] the tracks
✅ got: 3
[🧪 test] a field its author left empty is not listed at all
[⚙️ assert] an empty artist
✅ got: #f
[⚙️ assert] an album that was never read
✅ got: #f
[⚙️ assert] a key with no name
✅ got: #f
[🧪 test] every field a tab's author filled in is listed
[⚙️ assert] the title
✅ got: a title
[⚙️ assert] the artist
✅ got: an artist
[⚙️ assert] the album
✅ got: an album
[⚙️ assert] who tabbed it
✅ got: a tabber
[🧪 test] a tempo is written as a reader reads it
[⚙️ assert] a tempo in beats per minute
✅ got: 132 bpm
+----------------------------------------------+
[📑 suite] The section a version's tab is offered in
[🧪 test] a version carrying no tab is offered none
[⚙️ assert] a field a reader never touched
✅ got: ()
[⚙️ assert] and a version whose tab is not a tab at all
✅ got: ()
[🧪 test] the controls and the score are on the page before they are used
[⚙️ assert] the controls start hidden
✅ got: #t
[⚙️ assert] the place a score is drawn in
✅ got: #t
[🧪 test] each version's section is named after the version it is
[⚙️ assert] the score of the second version
✅ got: #t
[⚙️ assert] and not the first version's
✅ got: #t
[🧪 test] playing and stopping are offered once there is something to play
[⚙️ assert] playing
✅ got: #t
[⚙️ assert] and stopping
✅ got: #t
[🧪 test] and the offer to draw it here, which is a button and not a script
[⚙️ assert] the button
✅ got: #t
[🧪 test] a tab whose header cannot be read says so and is still a download
[⚙️ assert] the sentence
✅ got: #t
[🧪 test] the words the player shows are the page's own
[⚙️ assert] while it loads
✅ got: #t
[⚙️ assert] once it is drawn
✅ got: #t
[⚙️ assert] and when it cannot be drawn
✅ got: #t
[⚙️ assert] the state while loading is a place the script reaches
✅ got: #t
[⚙️ assert] and the state once drawn
✅ got: #t
[⚙️ assert] and the state that failed
✅ got: #t
[🧪 test] a version carrying a tab offers it for download
[⚙️ assert] the link to the file
✅ got: #t
[⚙️ assert] and the name it is saved under
✅ got: #t
+----------------------------------------------+
[📑 suite] The name a tab is saved under
[🧪 test] a name is still made of nothing
[⚙️ assert] a version with no name of its own
✅ got: wikimusic-tab.gp5
[⚙️ assert] and a song the page has not read yet
✅ got: wikimusic-tab.gp5
[🧪 test] the characters a file name cannot hold are replaced
[⚙️ assert] a song whose name holds a slash and a colon
✅ got: A-B- C-D-.gp5
[🧪 test] the name says which song and which version it is
[⚙️ assert] the two together
✅ got: A Song - v1.gp5
ℹ️ info - ran 3 tasks in: 0 seconds, 42796 microseconds
running unit tests from file: error-pages-spec.scm, entrypoint: spec
ℹ️ info - shuffled with seed 935548772 (replay with VERITAS_SEED=935548772)
+----------------------------------------------+
[📑 suite] What an error page says
[🧪 test] an error page describes what the reader can do
[⚙️ assert] a 500 offers to be tried again
✅ got: #t
[⚙️ assert] a 404 points at what is still there
✅ got: #t
[🧪 test] a page leads with a headline of its own
[⚙️ assert] a 404 has a headline
✅ got: #t
[⚙️ assert] and something to explain itself
✅ got: #t
[⚙️ assert] the two codes do not share wording
✅ got: #t
[⚙️ assert] nor do their explanations
✅ got: #t
[🧪 test] a page is drawn in the tone its class of refusal is read in
[⚙️ assert] a 502 is read as a 500
✅ got: warning
[⚙️ assert] a 503 is read as a 500
✅ got: warning
[⚙️ assert] a 418 is read as a 400
✅ got: plugs
[⚙️ assert] a 451 is read as a 400
✅ got: plugs
[⚙️ assert] a request that timed out has a page
✅ got: #t
[🧪 test] a status with no wording of its own still describes itself
[⚙️ assert] an unnamed status is named in its headline
✅ got: #t
[⚙️ assert] and it still has an explanation
✅ got: #t
[⚙️ assert] which is not the wording of a status that has one
✅ got: #t
+----------------------------------------------+
[📑 suite] The classes an error page asks for
[🧪 test] every class the page names is one the sheets define
[⚙️ assert] no class is a no-op on the page
✅ got: ()
[⚙️ assert] and the page does name a fair few
✅ got: #t
+----------------------------------------------+
[📑 suite] Rendering an error page
[🧪 test] the wording is translated through the procedure it is given
[⚙️ assert] the headline arrives wrapped
✅ got: #t
[⚙️ assert] and so does the detail
✅ got: #t
[🧪 test] the reader is offered a way out
[⚙️ assert] a link to /
✅ got: #t
[⚙️ assert] a link to /songs
✅ got: #t
[⚙️ assert] a link to /artists
✅ got: #t
[⚙️ assert] a link to /genres
✅ got: #t
[🧪 test] the page is a whole document of the site's own shell
[⚙️ assert] it starts with a doctype
✅ got: #t
[⚙️ assert] it is titled a WikiMusic page
✅ got: #t
[⚙️ assert] it links the site's stylesheet
✅ got: #t
[⚙️ assert] it carries the navigation
✅ got: #t
[⚙️ assert] and the footer
✅ got: #t
[🧪 test] the page is serialized the way the rest of the site is
[⚙️ assert] inline script survives the serializer
✅ got: #t
[⚙️ assert] and is not escaped into nonsense
✅ got: #t
[🧪 test] a reader who is signed in is not invited to sign in again
[⚙️ assert] the navigation differs with the session
✅ got: #t
[🧪 test] the page says which status it is
[⚙️ assert] a 400 page shows its code
✅ got: #t
[⚙️ assert] a 400 page leads with its own wording
✅ got: #t
[⚙️ assert] a 400 page is drawn with a glyph
✅ got: #t
[⚙️ assert] a 401 page shows its code
✅ got: #t
[⚙️ assert] a 401 page leads with its own wording
✅ got: #t
[⚙️ assert] a 401 page is drawn with a glyph
✅ got: #t
[⚙️ assert] a 403 page shows its code
✅ got: #t
[⚙️ assert] a 403 page leads with its own wording
✅ got: #t
[⚙️ assert] a 403 page is drawn with a glyph
✅ got: #t
[⚙️ assert] a 404 page shows its code
✅ got: #t
[⚙️ assert] a 404 page leads with its own wording
✅ got: #t
[⚙️ assert] a 404 page is drawn with a glyph
✅ got: #t
[⚙️ assert] a 408 page shows its code
✅ got: #t
[⚙️ assert] a 408 page leads with its own wording
✅ got: #t
[⚙️ assert] a 408 page is drawn with a glyph
✅ got: #t
[⚙️ assert] a 500 page shows its code
✅ got: #t
[⚙️ assert] a 500 page leads with its own wording
✅ got: #t
[⚙️ assert] a 500 page is drawn with a glyph
✅ got: #t
ℹ️ info - ran 3 tasks in: 1 seconds, -261981 microseconds
running unit tests from file: config-spec.scm, entrypoint: spec
ℹ️ info - shuffled with seed 935548772 (replay with VERITAS_SEED=935548772)
+----------------------------------------------+
[📑 suite] The picture a page is announced with
[🧪 test] and with no host to build one on, no address is invented
[⚙️ assert] no origin, no address
✅ got: #f
[🧪 test] an address the deployment gave is left alone
[⚙️ assert] an address is already one
✅ got: https://pictures.example.org/cover.jpg
[🧪 test] a path is turned into an address of the instance serving it
[⚙️ assert] the path is joined onto the origin
✅ got: https://example.org/img/cover.jpg
+----------------------------------------------+
[📑 suite] Where the instance reads what it says about itself
[🧪 test] a variable the deployment set is the one read
[⚙️ assert] the deployment's own value wins
✅ got: https://pingwing.jointhefreeworld.org
[🧪 test] a variable the deployment did not set falls back
[⚙️ assert] the project's own value is used
✅ got: the project's own value
+----------------------------------------------+
[📑 suite] The control an instance turns off
[🧪 test] an instance that says nothing about it asks
[⚙️ assert] the ask is shown unless it says otherwise
✅ got: #t
[🧪 test] an instance that raises nothing says so with #f
[⚙️ assert] the switch itself
✅ got: #f
[🧪 test] and words that are not a no leave it on
[⚙️ assert] yes
✅ got: #t
[⚙️ assert] or the switch itself
✅ got: #t
[🧪 test] and may say so in words
[⚙️ assert] false
✅ got: #f
[⚙️ assert] No, however it is capitalised
✅ got: #f
[⚙️ assert] off, with room around it
✅ got: #f
[⚙️ assert] 0
✅ got: #f
+----------------------------------------------+
[📑 suite] Where the interface's own files are read from
[🧪 test] a deployment that serves them elsewhere says where
[⚙️ assert] every file hangs off the host it named
✅ got: https://assets.example.org/wikimusic/vendor/fonts/lucida.css
[🧪 test] a host written with a trailing slash does not double it
[⚙️ assert] one slash between the two
✅ got: https://assets.example.org/wikimusic/favicon.ico
[🧪 test] an instance serving its own reads them off itself
[⚙️ assert] a path on this instance
✅ got: /vendor/fonts/lucida.css
[⚙️ assert] and the page's own stylesheet with it
✅ got: /css/wikimusic.olive.min.css
[🧪 test] a file named with a leading slash is one file
[⚙️ assert] the leading slash is not doubled
✅ got: /css/wikimusic.olive.min.css
+----------------------------------------------+
[📑 suite] The manual is read where the code is
[🧪 test] the manual's address is the deployment's, or the README beside its code
[⚙️ assert] the manual follows the code
✅ got: https://codeberg.org/jjba23/wikimusic/src/branch/trunk/README.org
+----------------------------------------------+
[📑 suite] The addresses the instance offers
[🧪 test] the mail an instance sends comes from an address
[⚙️ assert] an address, not a blank
✅ got: #t
[⚙️ assert] and a name beside it
✅ got: #t
[🧪 test] each one is an absolute address
[⚙️ assert] where the source is read
✅ got: #t
[⚙️ assert] where the manual is read
✅ got: #t
[⚙️ assert] where the licence is written out
✅ got: #t
[⚙️ assert] where a reader supports the project
✅ got: #t
[🧪 test] the copyright line names somebody
[⚙️ assert] the holder is named
✅ got: #t
ℹ️ info - ran 6 tasks in: 0 seconds, 23041 microseconds
running unit tests from file: views-spec.scm, entrypoint: spec
ℹ️ info - shuffled with seed 935548772 (replay with VERITAS_SEED=935548772)
+----------------------------------------------+
[📑 suite] A page a reader is reading
[🧪 test] the navigation is left to scroll away while reading
[⚙️ assert] the bar is still rendered
✅ got: #t
[⚙️ assert] nothing pins it
✅ got: #f
[⚙️ assert] no offset holds it there
✅ got: #f
[⚙️ assert] and it does not float above the text
✅ got: #f
[🧪 test] the navigation is pinned while browsing
[⚙️ assert] the bar keeps its place
✅ got: #t
[⚙️ assert] just below the top edge
✅ got: #t
[🧪 test] the whole template carries the reading mode down to the bar
[⚙️ assert] a reading page still has a bar
✅ got: #t
[⚙️ assert] but nothing pins it
✅ got: #f
[⚙️ assert] and every other page keeps it
✅ got: #t
+----------------------------------------------+
[📑 suite] The languages a page offers
[🧪 test] every offered language is one this instance ships
[⚙️ assert] the four catalogs are offered
✅ got: (en_US nl_NL es_ES ja_JP)
[🧪 test] each language is named in that language
[⚙️ assert] en_US is named
✅ got: #t
[⚙️ assert] nl_NL is named
✅ got: #t
[⚙️ assert] es_ES is named
✅ got: #t
[⚙️ assert] ja_JP is named
✅ got: #t
[🧪 test] a compiled catalog stands behind each offered language
[⚙️ assert] en_US has a catalog
✅ got: #t
[⚙️ assert] nl_NL has a catalog
✅ got: #t
[⚙️ assert] es_ES has a catalog
✅ got: #t
[⚙️ assert] ja_JP has a catalog
✅ got: #t
+----------------------------------------------+
[📑 suite] The footer
[🧪 test] no class is asked for twice
[⚙️ assert] a clean class attribute
✅ got: #f
[🧪 test] it offers the way back to the top
[⚙️ assert] the control says what it does
✅ got: #t
[⚙️ assert] and it is the one that scrolls
✅ got: #t
[🧪 test] it states the version this instance runs
[⚙️ assert] the version is named
✅ got: #t
[🧪 test] it points at the code, the manual and the licence
[⚙️ assert] where the source is
✅ got: #t
[⚙️ assert] where the manual is
✅ got: #t
[⚙️ assert] and where the licence is written out
✅ got: #t
+----------------------------------------------+
[📑 suite] A page a browser can read
[🧪 test] the body the caller passed is the body that is served
[⚙️ assert] the content survives
✅ got: #t
[⚙️ assert] and it stands inside the document
✅ got: #t
[🧪 test] no non-void element is closed as a self-closed tag
[⚙️ assert] each one is closed, not reopened
✅ got: #f
+----------------------------------------------+
[📑 suite] Where the shell reads its own files
[🧪 test] an instance that serves its own reads them off itself
[⚙️ assert] the stylesheet the page is laid out with
✅ got: #t
[⚙️ assert] and the icon the tab is marked with
✅ got: #t
[🧪 test] no file of the interface is somebody else's to serve
[⚙️ assert] no font host
✅ got: #f
[⚙️ assert] and none of the hosts a face was fetched from
✅ got: #f
[⚙️ assert] no icon CDN
✅ got: #f
[⚙️ assert] and no typeface read from somebody's static assets
✅ got: #f
[🧪 test] a deployment that serves them elsewhere moves every one of them
[⚙️ assert] the stylesheet
✅ got: #t
[⚙️ assert] the type the page is set in
✅ got: #t
[⚙️ assert] the glyphs its controls wear
✅ got: #t
[⚙️ assert] and the icon, with no address left behind
✅ got: #t
+----------------------------------------------+
[📑 suite] Choosing a language
[🧪 test] the control posts the choice to the preference route
[⚙️ assert] the form names the route
✅ got: #t
[⚙️ assert] the menu is the posted field
✅ got: #t
[🧪 test] every offered language is a row of the menu
[⚙️ assert] en_US is offered
✅ got: #t
[⚙️ assert] nl_NL is offered
✅ got: #t
[⚙️ assert] es_ES is offered
✅ got: #t
[⚙️ assert] ja_JP is offered
✅ got: #t
[🧪 test] the control names itself for a reader and for assistive tech
[⚙️ assert] the label is bound to the menu
✅ got: #t
[⚙️ assert] and says what it is
✅ got: #t
[🧪 test] the page states the language it is being read in
[⚙️ assert] the language being read is the row shown
✅ got: #t
[⚙️ assert] and a language not being read is not
✅ got: #f
ℹ️ info - ran 6 tasks in: 1 seconds, -722813 microseconds
running unit tests from file: auth-spec.scm, entrypoint: spec
ℹ️ info - shuffled with seed 935548772 (replay with VERITAS_SEED=935548772)
+----------------------------------------------+
[📑 suite] The permissions column
[🧪 test] the column is read, never run
[⚙️ assert] a form naming a procedure comes back as data
✅ got: (system rm -rf /)
[⚙️ assert] and so does a lambda
✅ got: (lambda (x) x)
[🧪 test] a row written before the change still yields its permissions
[⚙️ assert] a quasiquoted list is unwrapped
✅ got: (wikimusic:song-create)
[⚙️ assert] the long spelling of it too
✅ got: (wikimusic:song-create)
[🧪 test] a list of permissions is read as written
[⚙️ assert] one permission
✅ got: (wikimusic:song-create)
[⚙️ assert] several, in order
✅ got: (wikimusic:song-create wikimusic:artist-delete)
[🧪 test] anything that is not a list of permissions is an empty list
[⚙️ assert] an empty column
✅ got: ()
[⚙️ assert] no column at all
✅ got: ()
[⚙️ assert] an unreadable column
✅ got: ()
[⚙️ assert] a bare symbol
✅ got: ()
[⚙️ assert] a string
✅ got: ()
+----------------------------------------------+
[📑 suite] Passwords on file
[🧪 test] the same password hashed twice is never the same hash
[⚙️ assert] the salt is drawn fresh every time
✅ got: #t
[🧪 test] the recorded cost is at least the cost that was chosen
[⚙️ assert] argon2id does at least two passes
✅ got: #t
[⚙️ assert] and asks for at least 19 MiB a pass
✅ got: #t
[⚙️ assert] pbkdf2 does at least 210000 iterations
✅ got: #t
[🧪 test] a hash names the scheme it was derived with
[⚙️ assert] an argon2id hash says so
✅ got: argon2id
[⚙️ assert] and carries iterations, memory, lanes, salt and key
✅ got: 6
[⚙️ assert] a pbkdf2 hash says so
✅ got: pbkdf2-sha256
[⚙️ assert] and carries iterations, salt and key
✅ got: 4
+----------------------------------------------+
[📑 suite] Reading a session
[🧪 test] every way of failing is the same noerror decrypting token: misc-error: (#f ~A (open-envelope: not a bewaking envelope (expected BWK3).) #f)error decrypting token: misc-error: (#f ~A (open-envelope: not a bewaking envelope (expected BWK3).) #f)
[⚙️ assert] no token at all
✅ got: #f
[⚙️ assert] a false token
✅ got: #f
[⚙️ assert] an empty cookie
✅ got: #f
[⚙️ assert] a cookie that is not even an envelope
✅ got: #f
[⚙️ assert] a cookie of some other shape
✅ got: #f
[🧪 test] a token sealed with the project's keys names its owner
[⚙️ assert] the owner's identifier comes back
✅ got: 8b3f6a1e-1c2d-4e5f-8a9b-0c1d2e3f4a5b
[⚙️ assert] and the permissions it was sealed with
✅ got: (wikimusic:song-create)
[⚙️ assert] the payload carries its own expiry
✅ got: #t
[🧪 test] a token that has been altered does not openerror decrypting token: misc-error: (#f ~A (bewaking: the sealed data could not be opened: unknown, retired or wrong key, or the data was altered.) #f)error decrypting token: misc-error: (#f ~A (bewaking: the sealed data could not be opened: unknown, retired or wrong key, or the data was altered.) #f)
[⚙️ assert] a truncated token
✅ got: #f
[⚙️ assert] a token with something appended
✅ got: #f
[🧪 test] an expired session is no session
[⚙️ assert] a validity that is already behind it
✅ got: #f
[🧪 test] a token sealed under another key is not ourserror decrypting token: misc-error: (#f ~A (bewaking: the sealed data could not be opened: unknown, retired or wrong key, or the data was altered.) #f)error decrypting token: misc-error: (#f ~A (bewaking: the sealed data could not be opened: unknown, retired or wrong key, or the data was altered.) #f)
[⚙️ assert] a token sealed with a key we do not hold
✅ got: #f
[⚙️ assert] nor one signed with another
✅ got: #f
[🧪 test] a route's permission has to be one the session holds
[⚙️ assert] the permission it holds is granted
✅ got: #t
[⚙️ assert] and so is a set it holds in full
✅ got: #t
[⚙️ assert] one it does not hold is refused
✅ got: #f;;; note: auto-compilation is enabled, set GUILE_AUTO_COMPILE=0
;;; or pass the --no-auto-compile argument to disable.
;;; compiling /etc/byggsteg/job-clone/wikimusic/trunk/env.scm
;;; compiled /root/.cache/guile/ccache/3.0-LE-8-4.7/etc/byggsteg/job-clone/wikimusic/trunk/env.scm.go
;;; compiling ./test/veritas/unit/guitarpro-view-spec.scm
;;; compiling ./app/views/song-content-view.scm
;;; compiling ./lib/wikimusic/html.scm
WARNING: (hygguile hygguile): imported module (hygguile components select) overrides core binding `select'
;;; compiling ./lib/wikimusic/config.scm
;;; compiled /root/.cache/guile/ccache/3.0-LE-8-4.7/etc/byggsteg/job-clone/wikimusic/trunk/lib/wikimusic/config.scm.go
;;; compiling ./lib/wikimusic/seo.scm
;;; compiled /root/.cache/guile/ccache/3.0-LE-8-4.7/etc/byggsteg/job-clone/wikimusic/trunk/lib/wikimusic/seo.scm.go
WARNING: (wikimusic html): imported module (hygguile hygguile) overrides core binding `select'
WARNING: (wikimusic html): `get-short-commit-sha' imported from both (kracht prelude) and (hygguile hygguile)
;;; compiled /root/.cache/guile/ccache/3.0-LE-8-4.7/etc/byggsteg/job-clone/wikimusic/trunk/lib/wikimusic/html.scm.go
;;; compiling ./lib/wikimusic/domain.scm
;;; compiled /root/.cache/guile/ccache/3.0-LE-8-4.7/etc/byggsteg/job-clone/wikimusic/trunk/lib/wikimusic/domain.scm.go
;;; compiling ./lib/wikimusic/guitarpro.scm
;;; compiled /root/.cache/guile/ccache/3.0-LE-8-4.7/etc/byggsteg/job-clone/wikimusic/trunk/lib/wikimusic/guitarpro.scm.go
;;; compiling ./lib/wikimusic/domain/guitar-chord-guide.scm
;;; compiled /root/.cache/guile/ccache/3.0-LE-8-4.7/etc/byggsteg/job-clone/wikimusic/trunk/lib/wikimusic/domain/guitar-chord-guide.scm.go
WARNING: (app views song-content-view): imported module (hygguile hygguile) overrides core binding `select'
;;; compiled /root/.cache/guile/ccache/3.0-LE-8-4.7/etc/byggsteg/job-clone/wikimusic/trunk/app/views/song-content-view.scm.go
;;; compiled /root/.cache/guile/ccache/3.0-LE-8-4.7/etc/byggsteg/job-clone/wikimusic/trunk/test/veritas/unit/guitarpro-view-spec.scm.go
;;; compiling ./test/veritas/unit/error-pages-spec.scm
;;; compiling ./app/views/error.scm
;;; compiled /root/.cache/guile/ccache/3.0-LE-8-4.7/etc/byggsteg/job-clone/wikimusic/trunk/app/views/error.scm.go
;;; compiled /root/.cache/guile/ccache/3.0-LE-8-4.7/etc/byggsteg/job-clone/wikimusic/trunk/test/veritas/unit/error-pages-spec.scm.go
WARNING: (wikimusic html): `get-short-commit-sha' imported from both (kracht prelude) and (hygguile hygguile)
;;; compiling ./test/veritas/unit/config-spec.scm
;;; compiling ./test/support/deployment.scm
;;; compiled /root/.cache/guile/ccache/3.0-LE-8-4.7/etc/byggsteg/job-clone/wikimusic/trunk/test/support/deployment.scm.go
;;; compiled /root/.cache/guile/ccache/3.0-LE-8-4.7/etc/byggsteg/job-clone/wikimusic/trunk/test/veritas/unit/config-spec.scm.go
;;; compiling ./test/veritas/unit/views-spec.scm
;;; compiled /root/.cache/guile/ccache/3.0-LE-8-4.7/etc/byggsteg/job-clone/wikimusic/trunk/test/veritas/unit/views-spec.scm.go
WARNING: (wikimusic html): imported module (hygguile hygguile) overrides core binding `select'
;;; compiling ./test/veritas/unit/auth-spec.scm
;;; compiling ./lib/wikimusic/auth.scm
;;; compiled /root/.cache/guile/ccache/3.0-LE-8-4.7/etc/byggsteg/job-clone/wikimusic/trunk/lib/wikimusic/auth.scm.go
;;; compiled /root/.cache/guile/ccache/3.0-LE-8-4.7/etc/byggsteg/job-clone/wikimusic/trunk/test/veritas/unit/auth-spec.scm.go
;;; compiling ./test/veritas/unit/about-spec.scm
;;; compiling ./app/views/about.scm
;;; compiling ./lib/wikimusic/prose.scm
;;; compiled /root/.cache/guile/ccache/3.0-LE-8-4.7/etc/byggsteg/job-clone/wikimusic/trunk/lib/wikimusic/prose.scm.go
;;; compiling ./app/views/index.scm
;;; compiled /root/.cache/guile/ccache/3.0-LE-8-4.7/etc/byggsteg/job-clone/wikimusic/trunk/app/views/index.scm.go
;;; compiled /root/.cache/guile/ccache/3.0-LE-8-4.7/etc/byggsteg/job-clone/wikimusic/trunk/app/views/about.scm.go
;;; compiled
[⚙️ assert] with no permission asked for, a session is enough
✅ got: #t
+----------------------------------------------+
[📑 suite] Checking a password
[🧪 test] a hash was only derived from the one password
[⚙️ assert] a password missing its last character
✅ got: #f
[⚙️ assert] a password with the wrong case
✅ got: #f
[⚙️ assert] the empty password
✅ got: #f
[🧪 test] a row with no usable hash is refused rather than crashed on
[⚙️ assert] no hash is not a legacy hash
✅ got: #f
[⚙️ assert] nor is something that is not a string
✅ got: #f
[🧪 test] a password hashed before the change still verifies
[⚙️ assert] the right password still signs in
✅ got: #t
[⚙️ assert] the wrong one still does not
✅ got: #f
[⚙️ assert] and the row is recognisable as an old one
✅ got: #t
[⚙️ assert] while a derived hash is not
✅ got: #f
[🧪 test] the password a hash was derived from verifies
[⚙️ assert] the preferred scheme round trips
✅ got: #t
[⚙️ assert] and so does the fallback scheme
✅ got: #t
[⚙️ assert] the salt column no longer decides
✅ got: #t
[🧪 test] a hash that cannot be read is not a match
[⚙️ assert] no hash at all
✅ got: #f
[⚙️ assert] an argon2id salt that is not hexadecimal
✅ got: #f
[⚙️ assert] an argon2id cost that is not a number
✅ got: #f
[⚙️ assert] an argon2id hash with a field missing
✅ got: #f
[⚙️ assert] a pbkdf2 hash with a field missing
✅ got: #f
ℹ️ info - ran 4 tasks in: 0 seconds, 76101 microseconds
running unit tests from file: about-spec.scm, entrypoint: spec
ℹ️ info - shuffled with seed 935548772 (replay with VERITAS_SEED=935548772)
+----------------------------------------------+
[📑 suite] The word the link is offered in
[🧪 test] en_US has a word for it
[⚙️ assert] the label is translated
✅ got: #t
[🧪 test] ja_JP has a word for it
[⚙️ assert] the label is translated
✅ got: #t
[🧪 test] nl_NL has a word for it
[⚙️ assert] the label is translated
✅ got: #t
[🧪 test] es_ES has a word for it
[⚙️ assert] the label is translated
✅ got: #t
+----------------------------------------------+
[📑 suite] The front page as a way in
[🧪 test] both shelves of the collection are shown, songs first
[⚙️ assert] the songs are there
✅ got: #t
[⚙️ assert] and so are the artists
✅ got: #t
[⚙️ assert] in the order they are read
✅ got: #t
[⚙️ assert] with a way to the rest of them
✅ got: #t
[🧪 test] a page with no artists to show is still the whole page
[⚙️ assert] no heading over nothing
✅ got: #f
[🧪 test] the first screen says what is in the place
[⚙️ assert] the summary is read under the tagline
✅ got: #t
[⚙️ assert] and it is read after it
✅ got: #t
[🧪 test] the search control carries the words to the collection
[⚙️ assert] the form names the collection
✅ got: #t
[⚙️ assert] and the field its words are read from
✅ got: #t
+----------------------------------------------+
[📑 suite] What the page tells a crawler
[🧪 test] the title names the community
[⚙️ assert] the page is titled
✅ got: #t
[🧪 test] the description is the page's own one
[⚙️ assert] a search result shows the community
✅ got: #t
[🧪 test] the page says what kind of page it is
[⚙️ assert] the page states its own type
✅ got: #t
[⚙️ assert] and names the community as its subject
✅ got: #t
+----------------------------------------------+
[📑 suite] The words the two pages share
[🧪 test] the account of the project belongs to the about page
[⚙️ assert] the front page is still the collection
✅ got: #f
[🧪 test] both pages name the place the same way
[⚙️ assert] the about page says it
✅ got: #t
[⚙️ assert] and so does the front page
✅ got: #t
[🧪 test] both pages close on the same invitation
[⚙️ assert] the about page invites
✅ got: #t
[⚙️ assert] and so does the front page
✅ got: #t
[🧪 test] the one ask is the same control on both pages
[⚙️ assert] the about page asks
✅ got: #t
[⚙️ assert] and so does the front page
✅ got: #t
[⚙️ assert] offered in the project's own words
✅ got: #t
+----------------------------------------------+
[📑 suite] What the page says the project is
[🧪 test] it names what the project is
[⚙️ assert] the community is named
✅ got: #t
[⚙️ assert] and what it works towards
✅ got: #t
[🧪 test] it introduces the list of what a reader can do
[⚙️ assert] the list is introduced
✅ got: #t
[🧪 test] it ends on an invitation
[⚙️ assert] the last line is the invitation
✅ got: #t
[🧪 test] it says what the project is built on
[⚙️ assert] the language is named
✅ got: #t
[⚙️ assert] and so is the architecture
✅ got: #t
+----------------------------------------------+
[📑 suite] The order the page says things in
[🧪 test] the list stands under the sentence that introduces it
[⚙️ assert] the introduction comes first
✅ got: #t
[🧪 test] the account of the place comes before the ask
[⚙️ assert] nothing is asked for before anything is said
✅ got: #t
[🧪 test] the promises keep the order they were written in
[⚙️ assert] each one follows the one before it
✅ got: #t
+----------------------------------------------+
[📑 suite] The pages of an instance that asks for nothing
[🧪 test] what the pages are for is still said
[⚙️ assert] the promises are still made
✅ got: #t
[⚙️ assert] and the front page still invites
✅ got: #t
[🧪 test] and the front page asks for nothing either
[⚙️ assert] there is no control
✅ got: #f
[⚙️ assert] and none of the words that are on one
✅ got: #f
[🧪 test] the about page asks for nothing
[⚙️ assert] there is no control
✅ got: #f
[⚙️ assert] and none of the words that are on one
✅ got: #f
[⚙️ assert] and nothing about what the money pays for
✅ got: #f
+----------------------------------------------+
[📑 suite] The link to the page
[🧪 test] every page offers it
[⚙️ assert] the footer links to it
✅ got: #t
[⚙️ assert] and says where it leads
✅ got: #t
+----------------------------------------------+
[📑 suite] What the page promises a reader can do
[🧪 test] “Discover music from diverse genres and t…” is promised
[⚙️ assert] it is an item of the list
✅ got: #t
[⚙️ assert] and it is said once
✅ got: 1
[🧪 test] “Contribute by adding new compositions, a…” is promised
[⚙️ assert] it is an item of the list
✅ got: #t
[⚙️ assert] and it is said once
✅ got: 1
[🧪 test] “Organize information with structured met…” is promised
[⚙️ assert] it is an item of the list
✅ got: #t
[⚙️ assert] and it is said once
✅ got: 1
[🧪 test] “Learn through curated resources that con…” is promised
[⚙️ assert] it is an item of the list
✅ got: #t
[⚙️ assert] and it is said once
✅ got: 1
ℹ️ info - ran 9 tasks in: 1 seconds, -223680 microseconds
running unit tests from file: domain-spec.scm, entrypoint: spec
ℹ️ info - shuffled with seed 935548772 (replay with VERITAS_SEED=935548772)
+----------------------------------------------+
[📑 suite] Which related rows belong to an entry
[🧪 test] the identifier compared against is the entry's own
[⚙️ assert] a row naming a song is not the artist's row
✅ got: ()
[⚙️ assert] nor is another entry's row this one's
✅ got: ()
[🧪 test] a song is matched on the song's identifier
[⚙️ assert] the row naming this song
✅ got: (((artist-identifier . artist-1) (genre-identifier . genre-1) (song-identifier . song-1) (display-name . the one that matches)))
[🧪 test] a genre is matched on the genre's identifier
[⚙️ assert] the row naming this genre
✅ got: (((artist-identifier . artist-1) (genre-identifier . genre-1) (song-identifier . song-1) (display-name . the one that matches)))
[🧪 test] an artist is matched on the artist's identifier
[⚙️ assert] the row naming this artist
✅ got: (((artist-identifier . artist-1) (genre-identifier . genre-1) (song-identifier . song-1) (display-name . the one that matches)))
+----------------------------------------------+
[📑 suite] What a sort control means
[🧪 test] a sort that is spelled like SQL is not passed through
[⚙️ assert] "views desc; DROP TABLE songs" sorts by default
✅ got: ((last-edited-at desc))
[⚙️ assert] "display-name asc --" sorts by default
✅ got: ((last-edited-at desc))
[⚙️ assert] "(drop-table)" sorts by default
✅ got: ((last-edited-at desc))
[⚙️ assert] "views\") desc" sorts by default
✅ got: ((last-edited-at desc))
[🧪 test] something the page does not offer sorts the default way
[⚙️ assert] an unknown sort falls back
✅ got: ((last-edited-at desc))
[⚙️ assert] so does an empty one
✅ got: ((last-edited-at desc))
[🧪 test] every sort the pages offer maps onto its own clause
[⚙️ assert] newest first
✅ got: ((created-at desc))
[⚙️ assert] oldest first
✅ got: ((created-at asc))
[⚙️ assert] edited most recently
✅ got: ((last-edited-at desc))
[⚙️ assert] edited longest ago
✅ got: ((last-edited-at asc))
[⚙️ assert] by name, descending
✅ got: ((display-name desc))
[⚙️ assert] by name, ascending
✅ got: ((display-name asc))
[⚙️ assert] most viewed first
✅ got: ((views desc))
[⚙️ assert] least viewed first
✅ got: ((views asc))
[🧪 test] the clause names a column and a direction, and nothing else
[⚙️ assert] the column is a symbol
✅ got: #t
[⚙️ assert] the direction is one of the two words
✅ got: #t
[⚙️ assert] and a clause is all there is to it
✅ got: 2
ℹ️ info - ran 2 tasks in: 0 seconds, 20908 microseconds
running unit tests from file: assets-spec.scm, entrypoint: spec
ℹ️ info - shuffled with seed 935548772 (replay with VERITAS_SEED=935548772)
+----------------------------------------------+
[📑 suite] A face is named after what it carries
[🧪 test] the family, the subset and the slope are all in the name
[⚙️ assert] a straight face names no slope
✅ got: worksans-latin.woff2
[⚙️ assert] a sloped one says so
✅ got: sourceserif4-latin-italic.woff2
[⚙️ assert] and a subset that is not latin is kept apart
✅ got: worksans-latin-ext-italic.woff2
+----------------------------------------------+
[📑 suite] An icon set is copied with what is served of it
[🧪 test] a stylesheet with nothing to prune is left alone
[⚙️ assert] the text comes back as it went in
✅ got: @font-face {
font-family: "Phosphor";
src:
url("./Phosphor.woff2") format("woff2");
font-weight: normal;
font-style: normal;
font-display: block;
}
.ph {
font-family: "Phosphor" !important;
line-height: 1;
}
[🧪 test] what is left is still a stylesheet
[⚙️ assert] the braces still balance
✅ got: 2
[⚙️ assert] and they close as often
✅ got: 2
[⚙️ assert] the face keeps its family
✅ got: #t
[⚙️ assert] and the class the glyphs are worn with
✅ got: #t
[⚙️ assert] no clause is left dangling
✅ got: #t
[🧪 test] the face names the file that is beside it, and only it
[⚙️ assert] the woff2 clause ends the list
✅ got: #t
[⚙️ assert] the older woff is not named
✅ got: #t
[⚙️ assert] nor the ttf
✅ got: #t
[⚙️ assert] nor the svg
✅ got: #t
+----------------------------------------------+
[📑 suite] A fetched stylesheet names the files beside it
[🧪 test] each face is named for the block it stands in
[⚙️ assert] the addresses and the names line up
✅ got: ((https://fonts.gstatic.com/s/worksans/v25/aaa111.woff2 . worksans-latin-ext-italic.woff2) (https://fonts.gstatic.com/s/worksans/v25/bbb222.woff2 . worksans-latin.woff2) (https://fonts.gstatic.com/s/sourceserif4/v9/ccc333.woff2 . sourceserif4-latin-italic.woff2))
[🧪 test] every face the answer named is a face to fetch
[⚙️ assert] three faces, three files
✅ got: ((https://fonts.gstatic.com/s/worksans/v25/aaa111.woff2 . worksans-latin-ext-italic.woff2) (https://fonts.gstatic.com/s/worksans/v25/bbb222.woff2 . worksans-latin.woff2) (https://fonts.gstatic.com/s/sourceserif4/v9/ccc333.woff2 . sourceserif4-latin-italic.woff2))
[🧪 test] what the face says about itself is kept
[⚙️ assert] the format stays on the line
✅ got: #t
[⚙️ assert] so does what the face asks of the reader
✅ got: #t
[⚙️ assert] and the range it carries
✅ got: #t
[⚙️ assert] the line saying which subset follows
✅ got: #t
[⚙️ assert] and the stylesheet is as long as it was
✅ got: 27
[🧪 test] the address of the host it came from is gone
[⚙️ assert] no gstatic face is asked for
✅ got: #t
[⚙️ assert] nor any other absolute address
✅ got: #t
[⚙️ assert] the file beside it is named instead
✅ got: #t
ℹ️ info - ran 3 tasks in: 0 seconds, 25263 microseconds
running unit tests from file: controller-spec.scm, entrypoint: spec
ℹ️ info - shuffled with seed 935548772 (replay with VERITAS_SEED=935548772)
+----------------------------------------------+
[📑 suite] The guard a mistyped address reaches
[🧪 test] the answer says what was looked for, and where
[⚙️ assert] the message names the kind of thing and the identifier
✅ got: no genre with the identifier a-genre-that-is-not-there
[🧪 test] a lookup that found nothing is answered with a 404
[⚙️ assert] it raises rather than returning nothing
✅ got: #t
[⚙️ assert] with the code artanis renders a page for
✅ got: 404
[⚙️ assert] in the shape artanis reads: code, name, message, arguments
✅ got: #t
[⚙️ assert] the message is a format string
✅ got: #t
[⚙️ assert] and its arguments come as a list
✅ got: #t
[🧪 test] a lookup that found an empty list is a lookup that found nothing
[⚙️ assert] an empty list is answered like #f
✅ got: 404
[🧪 test] a row that was found is handed on as it is
[⚙️ assert] the very row comes back, not a copy
✅ got: #t
[⚙️ assert] and nothing about it is rewritten
✅ got: ((identifier . 1c9a5b7e-2f3a-4c5d-9e0f-1a2b3c4d5e6f) (display-name . blue))
[⚙️ assert] what it is called does not decide what it returns
✅ got: #t
+----------------------------------------------+
[📑 suite] The ownership half of who may change a row
[🧪 test] the super grant changes any row, whoever made it
[⚙️ assert] a row another user created
✅ got: #t
[⚙️ assert] and a row of its own
✅ got: #t
[🧪 test] a contributor changes the row they made
[⚙️ assert] their own row is theirs to change
✅ got: #t
[🧪 test] a contributor does not change a row somebody else made
[⚙️ assert] someone else's row is refused
✅ got: #f
[🧪 test] a row that names no creator is only the super grant's to change
[⚙️ assert] a row with no creator is refused
✅ got: #f
[⚙️ assert] while the super grant may still change it
✅ got: #t
ℹ️ info - ran 2 tasks in: 0 seconds, 22895 microseconds
running unit tests from file: sql-spec.scm, entrypoint: spec
ℹ️ info - shuffled with seed 935548772 (replay with VERITAS_SEED=935548772)
+----------------------------------------------+
[📑 suite] Sorting
[🧪 test] a direction that is not a direction is an error
[⚙️ assert] so an unknown word cannot reach the statement
✅ got: #t
[🧪 test] a clause renders its column and its direction
[⚙️ assert] two clauses, in order
✅ got: "views" DESC, "display_name" ASC
[⚙️ assert] a column with no direction renders bare
✅ got: "display_name"
[⚙️ assert] and a hyphen becomes an underscore there too
✅ got: "last_edited_at" DESC
[🧪 test] both spellings of a sort are accepted
[⚙️ assert] one clause, not yet wrapped
✅ got: ((display-name asc))
[⚙️ assert] a clause list is left as it is
✅ got: ((views desc) (display-name asc))
[⚙️ assert] and nothing sorts by nothing
✅ got: ()
+----------------------------------------------+
[📑 suite] Rendering a statement for the driver
[🧪 test] column names are quoted as identifiers
[⚙️ assert] a hyphenated symbol is spelled underscore
✅ got: UPDATE "users" SET "password" = 'argon2id$x' WHERE "identifier" = 'id-1'
[🧪 test] a value is rendered as a literal, and its quotes are doubled
[⚙️ assert] a string is quoted
✅ got: SELECT * FROM "songs" WHERE "display_name" = 'x'
[⚙️ assert] an apostrophe inside a value is doubled
✅ got: SELECT * FROM "songs" WHERE "display_name" = 'O''Brien'
[⚙️ assert] a value that means to end the string cannot
✅ got: SELECT * FROM "songs" WHERE "display_name" = '''; DROP TABLE users; --'
[⚙️ assert] so every quote it carries is paired
✅ got: 4
[🧪 test] a number and a boolean are not quoted
[⚙️ assert] a number is written as itself
✅ got: SELECT * FROM "songs" WHERE "views" = 3
[⚙️ assert] a true value is written as 1
✅ got: SELECT * FROM "songs" WHERE "views" = 1
[🧪 test] a statement string is passed through untouched
[⚙️ assert] a plain string
✅ got: SELECT 1
[⚙️ assert] a statement with a quoted value of its own
✅ got: SELECT 'x'
+----------------------------------------------+
[📑 suite] Shaping a row
[🧪 test] a column the attributes do not carry is simply left out
[⚙️ assert] so the table's own default can stand
✅ got: ((display-name . x))
[🧪 test] only the named columns survive, in the named order
[⚙️ assert] the columns decide, not the attributes
✅ got: ((views . 2) (display-name . x))
[🧪 test] a row holding none of the columns is an error
[⚙️ assert] an insert with no columns is refused
✅ got: #t
[🧪 test] an attribute for an unlisted column cannot reach the statement
[⚙️ assert] a key wikimusic never names is dropped
✅ got: ((display-name . x))
+----------------------------------------------+
[📑 suite] Matching a set of identifiers
[🧪 test] and a NOT IN over an empty set matches everything
[⚙️ assert] NOT IN () becomes a true predicate
✅ got: SELECT * FROM "songs" WHERE 1 = 1
[🧪 test] an IN clause binds one value per identifier, in order
[⚙️ assert] two identifiers
✅ got: SELECT * FROM "songs" WHERE "identifier" IN ('a', 'b')
[🧪 test] an empty set matches nothing rather than everything
[⚙️ assert] IN () becomes a false predicate
✅ got: SELECT * FROM "songs" WHERE 1 = 0
+----------------------------------------------+
[📑 suite] Matching a term
[🧪 test] a LIKE condition renders as LIKE, with its term bound
[⚙️ assert] a wildcard term
✅ got: SELECT * FROM "songs" WHERE "display_name" LIKE '%x%'
[🧪 test] the search statement a builder cannot write is still kracht's
[⚙️ assert] the apostrophe in the term is doubled
✅ got: #t
[⚙️ assert] no placeholder survives the substitution
✅ got: #f
[⚙️ assert] and the paging is written as numbers
✅ got: #t
+----------------------------------------------+
[📑 suite] Accounting for placeholders
[🧪 test] something that is neither a query nor a string is refused
[⚙️ assert] a number is not a statement
✅ got: #t
[⚙️ assert] nor is a symbol
✅ got: #t
[🧪 test] a rendered statement holds no placeholder any more
[⚙️ assert] no question mark survives the substitution
✅ got: #f
[⚙️ assert] and the rest of the statement is intact
✅ got: #t
[🧪 test] the number of values has to match the number of placeholders
[⚙️ assert] a value too few is an error rather than a guess
✅ got: #t
[⚙️ assert] a value too many is an error too
✅ got: #t
[⚙️ assert] a placeholder with no value is refused, not left behind
✅ got: #t
ℹ️ info - ran 6 tasks in: 0 seconds, 29570 microseconds
running unit tests from file: guitarpro-spec.scm, entrypoint: spec
ℹ️ info - shuffled with seed 935548772 (replay with VERITAS_SEED=935548772)
+----------------------------------------------+
[📑 suite] What the header of a Guitar Pro 5 file declares
[🧪 test] a field whose two lengths disagree is refused
[⚙️ assert] a string that claims a length it does not have
✅ got: #f
[🧪 test] a file of the right generation that is cut short is refused
[⚙️ assert] a header missing its last fields
✅ got: #f
[⚙️ assert] a file missing its whole header
✅ got: #f
[🧪 test] a file whose counts cannot be true is refused
[⚙️ assert] a negative measure count
✅ got: #f
[🧪 test] the reader consumes exactly the header and nothing more
[⚙️ assert] the plain file
✅ got: 1063
[⚙️ assert] a file with no title
✅ got: 1056
[⚙️ assert] a file with a long title
✅ got: 1093
[⚙️ assert] a file with two notice lines
✅ got: 1092
[⚙️ assert] a file of the earlier release
✅ got: 1058
[⚙️ assert] a file with numbers at their widest
✅ got: 1063
[🧪 test] every field of a written file is read back
[⚙️ assert] the release
✅ got: (5 10)
[⚙️ assert] the title
✅ got: a title
[⚙️ assert] the artist, two fields along
✅ got: an artist
[⚙️ assert] the notice lines, in order
✅ got: (first notice second notice)
[⚙️ assert] the lyrics
✅ got: (the words of the song)
[⚙️ assert] the tempo
✅ got: 132
[⚙️ assert] the key signature
✅ got: -2
[⚙️ assert] the key it names
✅ got: Bb
[⚙️ assert] the measures
✅ got: 42
[⚙️ assert] the tracks
✅ got: 3
[🧪 test] a header written by a release after 5.0 is read the same way
[⚙️ assert] the tempo of a 5.1 file
✅ got: 155
[⚙️ assert] the track count of a 5.1 file
✅ got: 5
[⚙️ assert] the tempo of a 5.0 file
✅ got: 155
[⚙️ assert] the track count of a 5.0 file
✅ got: 5
[🧪 test] a file that is not a readable Guitar Pro 5 file is answered with #f
[⚙️ assert] nothing at all
✅ got: #f
[⚙️ assert] a text file
✅ got: #f
[⚙️ assert] a pdf
✅ got: #f
[⚙️ assert] a Guitar Pro 4 file
✅ got: #f
+----------------------------------------------+
[📑 suite] What a base64 text spells
[🧪 test] a text that is not base64 is refused rather than guessed at
[⚙️ assert] a character outside the alphabet
✅ success catching error: misc-error: (#f base64-decode-string: not a base64 character ~S (*) #f)
[⚙️ assert] a text that ends in a lone character
✅ success catching error: misc-error: (#f base64-decode-string: the text ends in a lone base64 character () #f)
[🧪 test] a text broken across lines is read as one
[⚙️ assert] whitespace between the characters is ignored
✅ got: hello
[🧪 test] a text whose bytes are known decodes to them
[⚙️ assert] two characters
✅ got: h
[⚙️ assert] three characters
✅ got: hi
[⚙️ assert] five characters
✅ got: hello
[⚙️ assert] six characters is two whole groups
✅ got: hello!
[🧪 test] the encoding a browser does round-trips through the decoder
[⚙️ assert] one character, the shortest text that needs padding
✅ got: a
[⚙️ assert] one whole group of three
✅ got: abc
[⚙️ assert] a word
✅ got: wikimusic/root/.cache/guile/ccache/3.0-LE-8-4.7/etc/byggsteg/job-clone/wikimusic/trunk/test/veritas/unit/about-spec.scm.go
;;; compiling ./test/veritas/unit/domain-spec.scm
;;; compiled /root/.cache/guile/ccache/3.0-LE-8-4.7/etc/byggsteg/job-clone/wikimusic/trunk/test/veritas/unit/domain-spec.scm.go
;;; compiling ./test/veritas/unit/assets-spec.scm
;;; compiling ./resources/assets/fetch.scm
;;; compiled /root/.cache/guile/ccache/3.0-LE-8-4.7/etc/byggsteg/job-clone/wikimusic/trunk/resources/assets/fetch.scm.go
;;; compiled /root/.cache/guile/ccache/3.0-LE-8-4.7/etc/byggsteg/job-clone/wikimusic/trunk/test/veritas/unit/assets-spec.scm.go
;;; compiling ./test/veritas/unit/controller-spec.scm
;;; compiling ./lib/wikimusic/controller.scm
;;; compiling ./lib/wikimusic/sqlite/util.scm
;;; compiled /root/.cache/guile/ccache/3.0-LE-8-4.7/etc/byggsteg/job-clone/wikimusic/trunk/lib/wikimusic/sqlite/util.scm.go
;;; compiling ./lib/wikimusic/sqlite/comment.scm
;;; compiling ./lib/wikimusic/domain/comment.scm
;;; compiling ./lib/wikimusic/domain/repo.scm
;;; compiled /root/.cache/guile/ccache/3.0-LE-8-4.7/etc/byggsteg/job-clone/wikimusic/trunk/lib/wikimusic/domain/repo.scm.go
;;; compiled /root/.cache/guile/ccache/3.0-LE-8-4.7/etc/byggsteg/job-clone/wikimusic/trunk/lib/wikimusic/domain/comment.scm.go
;;; compiled /root/.cache/guile/ccache/3.0-LE-8-4.7/etc/byggsteg/job-clone/wikimusic/trunk/lib/wikimusic/sqlite/comment.scm.go
;;; compiled /root/.cache/guile/ccache/3.0-LE-8-4.7/etc/byggsteg/job-clone/wikimusic/trunk/lib/wikimusic/controller.scm.go
;;; compiled /root/.cache/guile/ccache/3.0-LE-8-4.7/etc/byggsteg/job-clone/wikimusic/trunk/test/veritas/unit/controller-spec.scm.go
;;; compiling ./test/veritas/unit/sql-spec.scm
;;; compiled /root/.cache/guile/ccache/3.0-LE-8-4.7/etc/byggsteg/job-clone/wikimusic/trunk/test/veritas/unit/sql-spec.scm.go
;;; compiling ./test/veritas/unit/guitarpro-spec.scm
;;; compiled /root/.cache/guile/ccache/3.0-LE-8-4.7/etc/byggsteg/job-clone/wikimusic/trunk/test/veritas/unit/guitarpro-spec.scm.go
[⚙️ assert] a text whose bytes are not its characters
✅ got: a title with an accent: café
+----------------------------------------------+
[📑 suite] The key a header names
[🧪 test] a number no signature has is not named at all
[⚙️ assert] one flat too many
✅ got: #f
[⚙️ assert] and one sharp too many
✅ got: #f
[🧪 test] each signature of the fifteen is named
[⚙️ assert] no accidentals is C
✅ got: C
[⚙️ assert] two flats is B flat
✅ got: Bb
[⚙️ assert] one sharp is G
✅ got: G
[⚙️ assert] seven sharps is C sharp
✅ got: C#
[⚙️ assert] seven flats is C flat
✅ got: Cb
+----------------------------------------------+
[📑 suite] What a data URL holds
[🧪 test] a base64 data URL is one, and its bytes come back
[⚙️ assert] a file posted by a browser
✅ got: #t
[⚙️ assert] the bytes after the comma
✅ got: hi
[🧪 test] a version's own file is read through its data URL
[⚙️ assert] the title
✅ got: through a data url
[⚙️ assert] the tempo
✅ got: 90
[🧪 test] an empty value is not a data URL
[⚙️ assert] the empty string
✅ got: #f
[⚙️ assert] nor is a path
✅ got: #f
[🧪 test] a data URL that holds something other than base64 holds no bytes
[⚙️ assert] a text data URL
✅ got: #f
[⚙️ assert] a base64 text that does not decode
✅ got: #f
ℹ️ info - ran 4 tasks in: 0 seconds, 37574 microseconds
finished running tests
--------------------------------------------------------------
Maak 0.14.0: the infinitely extensible command runner
Loading tasks from Maak file: /etc/byggsteg/job-clone/wikimusic/trunk/maak.scm
Executing Maak tasks: test
--------------------------------------------------------------
=> Running Maak task: test
Run the project's test suite, one spec at a time, from test/veritas.
Executing: guix time-machine --channels=channels.scm -- shell -m manifest.scm -- guile --fresh-auto-compile -L ./lib -L ./test -L . -c '(load "env.scm")(begin (setlocale LC_ALL "C.UTF-8") (use-modules (veritas runner)) (run-tests))'
→ running command: cd /etc/byggsteg/job-clone/wikimusic/trunk && maak migrate
-------------------------------------------------
Starting Mutastructura: guile -L /gnu/store/6av4jm5ld201j6v8dx1n0b5xv1m4d4cy-profile/share/guile/site/3.0 -c ((@(mutastructura main) main)) --database sqlite3 --connection wikimusic.db --migrations /etc/byggsteg/job-clone/wikimusic/trunk/resources/migrations/sqlite --resources /gnu/store/6av4jm5ld201j6v8dx1n0b5xv1m4d4cy-profile/share/resources
---------------------------------------------------------
MUTASTRUCTURA: Schema Migrations tooling powered by Lisp
🏛️ M U T A S T R U C T U R A 🏛️
_ _ V E N I • V I D I • L I S P _ _
FOUND MIGRATION HISTORY:
((id . 1) (name . "mutastructura-init.sql") (checksum . "9cc74e39062f44295eed10dcc318b67a87a2dc0ac9bc62eb76500fc4f3650e03") (applied-at . "2026-10-14 12:07:01"))
((id . 2) (name . "2026-04-19_23:14:00_init_wikimusic_schema.sql") (checksum . "482c173e392047e9b62d148459fb75465e1fc15ff9ec0f163969598069cd1c46") (applied-at . "2026-10-14 12:07:01"))
---------------------------------------------------------
NOTHING TO DO!
CLOSING DATABASE...
--------------------------------------------------------------
Maak 0.14.0: the infinitely extensible command runner
Loading tasks from Maak file: /etc/byggsteg/job-clone/wikimusic/trunk/maak.scm
Executing Maak tasks: migrate
--------------------------------------------------------------
=> Running Maak task: migrate
Apply the SQL migrations to the database in this directory with mutastructura.
Mutastructura does the applying: it keeps a ledger of what it has run in the
`migrations_history' table of the database, runs only the migrations that are
not in it and records each one with a SHA256 of its file, all inside a single
transaction. That is what makes this safe to run at any time, and it is also
the way to bring a database made by an earlier deployment up to what the source
in front of it expects. Mutastructura is the only thing that applies
migrations; no task reads the SQL files and hands them to sqlite3 itself.
Execute command:
guix time-machine --channels=channels.scm -- shell -m manifest.scm -- mutastructura --database sqlite3 --connection wikimusic.db --migrations resources/migrations/sqlite
Executing: guix time-machine --channels=channels.scm -- shell -m manifest.scm -- mutastructura --database sqlite3 --connection wikimusic.db --migrations resources/migrations/sqlite
→ running command: cd /etc/byggsteg/job-clone/wikimusic/trunk && sudo maak deploy
Warning (emacs): Cannot fontify source block (htmlize.el >= 1.34 required). Falling back to plain text. (see ‘org-html-htmlize-output-type’)
Warning (emacs): Cannot fontify source block (htmlize.el >= 1.34 required). Falling back to plain text. (see ‘org-html-htmlize-output-type’)
Warning (emacs): Cannot fontify source block (htmlize.el >= 1.34 required). Falling back to plain text. (see ‘org-html-htmlize-output-type’)
Warning (emacs): Cannot fontify source block (htmlize.el >= 1.34 required). Falling back to plain text. (see ‘org-html-htmlize-output-type’)
Warning (emacs): Cannot fontify source block (htmlize.el >= 1.34 required). Falling back to plain text. (see ‘org-html-htmlize-output-type’)
Warning (emacs): Cannot fontify source block (htmlize.el >= 1.34 required). Falling back to plain text. (see ‘org-html-htmlize-output-type’)
Warning (emacs): Cannot fontify source block (htmlize.el >= 1.34 required). Falling back to plain text. (see ‘org-html-htmlize-output-type’)
Warning (emacs): Cannot fontify source block (htmlize.el >= 1.34 required). Falling back to plain text. (see ‘org-html-htmlize-output-type’)
Warning (emacs): Cannot fontify source block (htmlize.el >= 1.34 required). Falling back to plain text. (see ‘org-html-htmlize-output-type’)
Warning (emacs): Cannot fontify source block (htmlize.el >= 1.34 required). Falling back to plain text. (see ‘org-html-htmlize-output-type’)
Warning (emacs): Cannot fontify source block (htmlize.el >= 1.34 required). Falling back to plain text. (see ‘org-html-htmlize-output-type’)
Warning (emacs): Cannot fontify source block (htmlize.el >= 1.34 required). Falling back to plain text. (see ‘org-html-htmlize-output-type’)
Warning (emacs): Cannot fontify source block (htmlize.el >= 1.34 required). Falling back to plain text. (see ‘org-html-htmlize-output-type’)
Warning (emacs): Cannot fontify source block (htmlize.el >= 1.34 required). Falling back to plain text. (see ‘org-html-htmlize-output-type’)
Warning (emacs): Cannot fontify source block (htmlize.el >= 1.34 required). Falling back to plain text. (see ‘org-html-htmlize-output-type’)
Warning (emacs): Cannot fontify source block (htmlize.el >= 1.34 required). Falling back to plain text. (see ‘org-html-htmlize-output-type’)
Warning (emacs): Cannot fontify source block (htmlize.el >= 1.34 required). Falling back to plain text. (see ‘org-html-htmlize-output-type’)
Warning (emacs): Cannot fontify source block (htmlize.el >= 1.34 required). Falling back to plain text. (see ‘org-html-htmlize-output-type’)
finished publishing Wikimusic manual
--------------------------------------------------------------
Maak 0.14.0: the infinitely extensible command runner
Loading tasks from Maak file: /etc/byggsteg/job-clone/wikimusic/trunk/maak.scm
Executing Maak tasks: deploy
--------------------------------------------------------------
=> Running Maak task: deploy
Deploy the project's documentation.
The manual is rendered from the README and served under `manuals/wikimusic',
which is where a reader of the site is sent.
Execute command:
guix time-machine --channels=channels.scm -- shell -m manifest.scm -- emacs -Q --batch --eval '(progn (require (quote ox-html)) (setq org-html-prefer-user-labels t) (find-file "./README.org") (org-html-export-to-html) (message "finished publishing Wikimusic manual"))'
Executing: guix time-machine --channels=channels.scm -- shell -m manifest.scm -- emacs -Q --batch --eval '(progn (require (quote ox-html)) (setq org-html-prefer-user-labels t) (find-file "./README.org") (org-html-export-to-html) (message "finished publishing Wikimusic manual"))'
removed '/srv/http/jointhefreeworld.org/manuals/wikimusic/index.html'
renamed 'README.html' -> '/srv/http/jointhefreeworld.org/manuals/wikimusic/index.html'
→ running command: cd /etc/byggsteg/job-clone/wikimusic/trunk && sudo herd enable wikimusic-daemon || true && sudo herd restart wikimusic-daemon || true
Enabled service wikimusic-daemon.
Service wikimusic-daemon has been started.