Compare commits
16
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
604ccd10d4 | ||
|
|
52d22f07d7 | ||
|
|
8f6a2b1f82 | ||
|
|
a0fbd6bcb5 | ||
|
|
5ce46cd197 | ||
|
|
8943a290f5 | ||
|
|
537e2ef31f | ||
|
|
545a172bbf | ||
|
|
391095ccb9 | ||
|
|
8536021f82 | ||
|
|
23307935b9 | ||
|
|
d5d6fd7412 | ||
|
|
2e69099354 | ||
|
|
5c65b1f3dc | ||
|
|
4f074e87c7 | ||
|
|
1968aba6e0 |
+303
@@ -34,3 +34,306 @@ modules/mesh-build-image/cmd/mesh-build-image/mesh-build-image
|
|||||||
/checks/paths/paths
|
/checks/paths/paths
|
||||||
/modules/mesh-build-go/mesh-build-go
|
/modules/mesh-build-go/mesh-build-go
|
||||||
/modules/mesh-delivery-gate/mesh-delivery-gate
|
/modules/mesh-delivery-gate/mesh-delivery-gate
|
||||||
|
|
||||||
|
# A binary built in place, named like its module directory or a cmd directory, in the module root or in that cmd directory
|
||||||
|
# (issue 1921, try speed S13), for every module; the lint step of every check refuses one a change adds, a module added
|
||||||
|
# later included.
|
||||||
|
/modules/adwaita/adwaita
|
||||||
|
/modules/adwaita/adwaita-tools
|
||||||
|
/modules/adwaita/cmd/adwaita-tools/adwaita-tools
|
||||||
|
/modules/artifact-store-tools/artifact-store-tools
|
||||||
|
/modules/artifact-store-tools/cmd/artifact-store-tools/artifact-store-tools
|
||||||
|
/modules/asus-zephyrus-g14/asus-zephyrus-g14
|
||||||
|
/modules/asus-zephyrus-g14/zephyrus
|
||||||
|
/modules/asus-zephyrus-g14/cmd/zephyrus/zephyrus
|
||||||
|
/modules/audit-logger/audit-logger
|
||||||
|
/modules/avahi/avahi
|
||||||
|
/modules/avahi/avahi-tools
|
||||||
|
/modules/avahi/cmd/avahi-tools/avahi-tools
|
||||||
|
/modules/baserow/baserow
|
||||||
|
/modules/blueman/blueman
|
||||||
|
/modules/blueman/blueman-tools
|
||||||
|
/modules/blueman/cmd/blueman-tools/blueman-tools
|
||||||
|
/modules/bluetooth/bluetooth
|
||||||
|
/modules/bluetooth/bluetooth-tools
|
||||||
|
/modules/bluetooth/cmd/bluetooth-tools/bluetooth-tools
|
||||||
|
/modules/brother/brother
|
||||||
|
/modules/brother/cmd/brother/brother
|
||||||
|
/modules/buildah/buildah
|
||||||
|
/modules/ca-trust/ca-trust
|
||||||
|
/modules/claude-code/claude-code
|
||||||
|
/modules/claude-code/cmd/claude-code/claude-code
|
||||||
|
/modules/claude-licence-manager/claude-licence-manager
|
||||||
|
/modules/claude-licence-manager/cmd/claude-licence-manager/claude-licence-manager
|
||||||
|
/modules/clipmenu/clipmenu
|
||||||
|
/modules/clipmenu/clipmenu-tools
|
||||||
|
/modules/clipmenu/cmd/clipmenu-tools/clipmenu-tools
|
||||||
|
/modules/confluence/confluence
|
||||||
|
/modules/cups/cups
|
||||||
|
/modules/cups/cups-tools
|
||||||
|
/modules/cups/cmd/cups-tools/cups-tools
|
||||||
|
/modules/dbus/dbus
|
||||||
|
/modules/dbus/dbus-tools
|
||||||
|
/modules/dbus/cmd/dbus-tools/dbus-tools
|
||||||
|
/modules/desk-channel/desk-channel
|
||||||
|
/modules/desk-channel/cmd/desk-channel/desk-channel
|
||||||
|
/modules/dev-tools/dev-tools
|
||||||
|
/modules/disk-load/disk-load
|
||||||
|
/modules/disk-load/cmd/disk-load/disk-load
|
||||||
|
/modules/distribution/distribution
|
||||||
|
/modules/dmenu/dmenu
|
||||||
|
/modules/dmenu/dmenu-tools
|
||||||
|
/modules/dmenu/cmd/dmenu-tools/dmenu-tools
|
||||||
|
/modules/dnsmasq/dnsmasq
|
||||||
|
/modules/dnsmasq/dnsmasq-tools
|
||||||
|
/modules/dnsmasq/cmd/dnsmasq-tools/dnsmasq-tools
|
||||||
|
/modules/docker/docker
|
||||||
|
/modules/docker/docker-tools
|
||||||
|
/modules/docker/cmd/docker-tools/docker-tools
|
||||||
|
/modules/docker-compose/docker-compose
|
||||||
|
/modules/docker-compose/docker-compose-tools
|
||||||
|
/modules/docker-compose/cmd/docker-compose-tools/docker-compose-tools
|
||||||
|
/modules/dunst/dunst
|
||||||
|
/modules/dunst/dunst-tools
|
||||||
|
/modules/dunst/cmd/dunst-tools/dunst-tools
|
||||||
|
/modules/fail2ban/fail2ban
|
||||||
|
/modules/fail2ban/fail2ban-tools
|
||||||
|
/modules/fail2ban/cmd/fail2ban-tools/fail2ban-tools
|
||||||
|
/modules/feh/feh
|
||||||
|
/modules/feh/feh-tools
|
||||||
|
/modules/feh/cmd/feh-tools/feh-tools
|
||||||
|
/modules/flatpak/flatpak
|
||||||
|
/modules/flatpak/flatpak-tools
|
||||||
|
/modules/flatpak/cmd/flatpak-tools/flatpak-tools
|
||||||
|
/modules/fonts/fonts
|
||||||
|
/modules/fonts/fonts-tools
|
||||||
|
/modules/fonts/cmd/fonts-tools/fonts-tools
|
||||||
|
/modules/forticlient/forticlient
|
||||||
|
/modules/forticlient/forticlient-tools
|
||||||
|
/modules/forticlient/cmd/forticlient-tools/forticlient-tools
|
||||||
|
/modules/gitea/gitea
|
||||||
|
/modules/gitea/agent-teams
|
||||||
|
/modules/gitea/cmd/agent-teams/agent-teams
|
||||||
|
/modules/gitea/agent-token
|
||||||
|
/modules/gitea/cmd/agent-token/agent-token
|
||||||
|
/modules/gitea/forge
|
||||||
|
/modules/gitea/cmd/forge/forge
|
||||||
|
/modules/gitea/forge-account-provider
|
||||||
|
/modules/gitea/cmd/forge-account-provider/forge-account-provider
|
||||||
|
/modules/gitea/npm-registry
|
||||||
|
/modules/gitea/cmd/npm-registry/npm-registry
|
||||||
|
/modules/gitea/repositories
|
||||||
|
/modules/gitea/cmd/repositories/repositories
|
||||||
|
/modules/gitlab/gitlab
|
||||||
|
/modules/gnome-keyring/gnome-keyring
|
||||||
|
/modules/gnome-keyring/gnome-keyring-tools
|
||||||
|
/modules/gnome-keyring/cmd/gnome-keyring-tools/gnome-keyring-tools
|
||||||
|
/modules/go/go
|
||||||
|
/modules/grafana/grafana
|
||||||
|
/modules/hello-web/hello-web
|
||||||
|
/modules/home-assistant/home-assistant
|
||||||
|
/modules/hostname/hostname
|
||||||
|
/modules/hostname/hostname-tools
|
||||||
|
/modules/hostname/cmd/hostname-tools/hostname-tools
|
||||||
|
/modules/i3/i3
|
||||||
|
/modules/i3/i3-tools
|
||||||
|
/modules/i3/cmd/i3-tools/i3-tools
|
||||||
|
/modules/i3status-rust/i3status-rust
|
||||||
|
/modules/i3status-rust/i3status-rust-tools
|
||||||
|
/modules/i3status-rust/cmd/i3status-rust-tools/i3status-rust-tools
|
||||||
|
/modules/icecast/icecast
|
||||||
|
/modules/influxdb/influxdb
|
||||||
|
/modules/invoicing/invoicing
|
||||||
|
/modules/jira/jira
|
||||||
|
/modules/kernel/kernel
|
||||||
|
/modules/kernel/kernel-tools
|
||||||
|
/modules/kernel/cmd/kernel-tools/kernel-tools
|
||||||
|
/modules/keycloak/keycloak
|
||||||
|
/modules/keycloak/keycloak-provider
|
||||||
|
/modules/keycloak/cmd/keycloak-provider/keycloak-provider
|
||||||
|
/modules/lab/lab
|
||||||
|
/modules/lemurs/lemurs
|
||||||
|
/modules/lemurs/lemurs-tools
|
||||||
|
/modules/lemurs/cmd/lemurs-tools/lemurs-tools
|
||||||
|
/modules/letta/letta
|
||||||
|
/modules/localization/localization
|
||||||
|
/modules/localization/localization-tools
|
||||||
|
/modules/localization/cmd/localization-tools/localization-tools
|
||||||
|
/modules/local-model-consumer/local-model-consumer
|
||||||
|
/modules/logrotate/logrotate
|
||||||
|
/modules/logrotate/logrotate-tools
|
||||||
|
/modules/logrotate/cmd/logrotate-tools/logrotate-tools
|
||||||
|
/modules/mailu/mailu
|
||||||
|
/modules/mailu/automx
|
||||||
|
/modules/mailu/cmd/automx/automx
|
||||||
|
/modules/marrytts/marrytts
|
||||||
|
/modules/matrix/matrix
|
||||||
|
/modules/memory-pressure/memory-pressure
|
||||||
|
/modules/memory-pressure/cmd/memory-pressure/memory-pressure
|
||||||
|
/modules/mesh-build-go/mesh-build-go
|
||||||
|
/modules/mesh-build-go/cmd/mesh-build-go/mesh-build-go
|
||||||
|
/modules/mesh-build-image/mesh-build-image
|
||||||
|
/modules/mesh-build-image/cmd/mesh-build-image/mesh-build-image
|
||||||
|
/modules/mesh-build-node/mesh-build-node
|
||||||
|
/modules/mesh-build-node/cmd/mesh-build-node/mesh-build-node
|
||||||
|
/modules/mesh-catalog/mesh-catalog
|
||||||
|
/modules/mesh-cli/mesh-cli
|
||||||
|
/modules/mesh-cli/cmd/mesh-cli/mesh-cli
|
||||||
|
/modules/mesh-config/mesh-config
|
||||||
|
/modules/mesh-config/cmd/mesh-config/mesh-config
|
||||||
|
/modules/mesh-delivery-gate/mesh-delivery-gate
|
||||||
|
/modules/mesh-delivery-gate/cmd/mesh-delivery-gate/mesh-delivery-gate
|
||||||
|
/modules/mesh-files/mesh-files
|
||||||
|
/modules/mesh-files/cmd/mesh-files/mesh-files
|
||||||
|
/modules/mesh-issues/mesh-issues
|
||||||
|
/modules/mesh-issues/cmd/mesh-issues/mesh-issues
|
||||||
|
/modules/mesh-knowledge/mesh-knowledge
|
||||||
|
/modules/mesh-knowledge/cmd/mesh-knowledge/mesh-knowledge
|
||||||
|
/modules/mesh-mail/mesh-mail
|
||||||
|
/modules/mesh-mail/cmd/mesh-mail/mesh-mail
|
||||||
|
/modules/mesh-mail-notifier/mesh-mail-notifier
|
||||||
|
/modules/mesh-mail-notifier/cmd/mesh-mail-notifier/mesh-mail-notifier
|
||||||
|
/modules/mesh-mail-reader/mesh-mail-reader
|
||||||
|
/modules/mesh-mail-reader/cmd/mesh-mail-reader/mesh-mail-reader
|
||||||
|
/modules/mesh-mail-web/mesh-mail-web
|
||||||
|
/modules/mesh-mail-web/cmd/mesh-mail-web/mesh-mail-web
|
||||||
|
/modules/mesh-provisioner/mesh-provisioner
|
||||||
|
/modules/mesh-provisioner/cmd/mesh-provisioner/mesh-provisioner
|
||||||
|
/modules/mesh-provisioner-client/mesh-provisioner-client
|
||||||
|
/modules/mesh-provisioner-client/cmd/mesh-provisioner-client/mesh-provisioner-client
|
||||||
|
/modules/mesh-reads/mesh-reads
|
||||||
|
/modules/mesh-reads/cmd/mesh-reads/mesh-reads
|
||||||
|
/modules/mesh-vault/mesh-vault
|
||||||
|
/modules/mesh-vault/cmd/mesh-vault/mesh-vault
|
||||||
|
/modules/mesh-watcher/mesh-watcher
|
||||||
|
/modules/mesh-watcher/cmd/mesh-watcher/mesh-watcher
|
||||||
|
/modules/messenger/messenger
|
||||||
|
/modules/messenger/cmd/messenger/messenger
|
||||||
|
/modules/minio/minio
|
||||||
|
/modules/minio/object-store
|
||||||
|
/modules/minio/cmd/object-store/object-store
|
||||||
|
/modules/model-usage/model-usage
|
||||||
|
/modules/mongodb/mongodb
|
||||||
|
/modules/mosquitto/mosquitto
|
||||||
|
/modules/mounts/mounts
|
||||||
|
/modules/mounts/cmd/mounts/mounts
|
||||||
|
/modules/mssql/mssql
|
||||||
|
/modules/n8n/n8n
|
||||||
|
/modules/nats/nats
|
||||||
|
/modules/nats/mesh-nats-snapshot
|
||||||
|
/modules/nats/cmd/mesh-nats-snapshot/mesh-nats-snapshot
|
||||||
|
/modules/nats/nats-tools
|
||||||
|
/modules/nats/cmd/nats-tools/nats-tools
|
||||||
|
/modules/netcheck/netcheck
|
||||||
|
/modules/netcheck/cmd/netcheck/netcheck
|
||||||
|
/modules/networkmanager/networkmanager
|
||||||
|
/modules/networkmanager/uplink-tools
|
||||||
|
/modules/networkmanager/cmd/uplink-tools/uplink-tools
|
||||||
|
/modules/nextcloud/nextcloud
|
||||||
|
/modules/nextcloud-client/nextcloud-client
|
||||||
|
/modules/nextcloud-client/nextcloud-client-tools
|
||||||
|
/modules/nextcloud-client/cmd/nextcloud-client-tools/nextcloud-client-tools
|
||||||
|
/modules/nfs-server/nfs-server
|
||||||
|
/modules/nfs-server/cmd/nfs-server/nfs-server
|
||||||
|
/modules/nftables/nftables
|
||||||
|
/modules/nm-applet/nm-applet
|
||||||
|
/modules/nm-applet/nm-applet-tools
|
||||||
|
/modules/nm-applet/cmd/nm-applet-tools/nm-applet-tools
|
||||||
|
/modules/node-env/node-env
|
||||||
|
/modules/nodejs/nodejs
|
||||||
|
/modules/nodered/nodered
|
||||||
|
/modules/ollama/ollama
|
||||||
|
/modules/only-office/only-office
|
||||||
|
/modules/only-office/cmd/only-office/only-office
|
||||||
|
/modules/openai-consumer/openai-consumer
|
||||||
|
/modules/openrazer/openrazer
|
||||||
|
/modules/openrazer/openrazer-tools
|
||||||
|
/modules/openrazer/cmd/openrazer-tools/openrazer-tools
|
||||||
|
/modules/pacman/pacman
|
||||||
|
/modules/pacman/pacman-tools
|
||||||
|
/modules/pacman/cmd/pacman-tools/pacman-tools
|
||||||
|
/modules/photos-eef/photos-eef
|
||||||
|
/modules/photos-filip/photos-filip
|
||||||
|
/modules/picom/picom
|
||||||
|
/modules/picom/picom-tools
|
||||||
|
/modules/picom/cmd/picom-tools/picom-tools
|
||||||
|
/modules/polychromatic/polychromatic
|
||||||
|
/modules/polychromatic/polychromatic-tools
|
||||||
|
/modules/polychromatic/cmd/polychromatic-tools/polychromatic-tools
|
||||||
|
/modules/postgres/postgres
|
||||||
|
/modules/postgres/postgres-provider
|
||||||
|
/modules/postgres/cmd/postgres-provider/postgres-provider
|
||||||
|
/modules/power/power
|
||||||
|
/modules/power/cmd/power/power
|
||||||
|
/modules/powerlevel10k/powerlevel10k
|
||||||
|
/modules/records/records
|
||||||
|
/modules/records/cmd/records/records
|
||||||
|
/modules/redis/redis
|
||||||
|
/modules/restic/restic
|
||||||
|
/modules/restic/restic-backups
|
||||||
|
/modules/restic/cmd/restic-backups/restic-backups
|
||||||
|
/modules/rofi/rofi
|
||||||
|
/modules/rofi/rofi-tools
|
||||||
|
/modules/rofi/cmd/rofi-tools/rofi-tools
|
||||||
|
/modules/route-adapter/route-adapter
|
||||||
|
/modules/route-adapter/route-provider
|
||||||
|
/modules/route-adapter/cmd/route-provider/route-provider
|
||||||
|
/modules/screen-lock/screen-lock
|
||||||
|
/modules/screen-lock/screen-lock-tools
|
||||||
|
/modules/screen-lock/cmd/screen-lock-tools/screen-lock-tools
|
||||||
|
/modules/searxng/searxng
|
||||||
|
/modules/sensors/sensors
|
||||||
|
/modules/sensors/cmd/sensors/sensors
|
||||||
|
/modules/showcase/showcase
|
||||||
|
/modules/slack/slack
|
||||||
|
/modules/slack/slack-tools
|
||||||
|
/modules/slack/cmd/slack-tools/slack-tools
|
||||||
|
/modules/snapd/snapd
|
||||||
|
/modules/snapd/snapd-tools
|
||||||
|
/modules/snapd/cmd/snapd-tools/snapd-tools
|
||||||
|
/modules/ssh-client/ssh-client
|
||||||
|
/modules/ssh-client/ssh-client-tools
|
||||||
|
/modules/ssh-client/cmd/ssh-client-tools/ssh-client-tools
|
||||||
|
/modules/sshd/sshd
|
||||||
|
/modules/step-ca/step-ca
|
||||||
|
/modules/sudo/sudo
|
||||||
|
/modules/sudo/sudo-tools
|
||||||
|
/modules/sudo/cmd/sudo-tools/sudo-tools
|
||||||
|
/modules/supabase/supabase
|
||||||
|
/modules/systemd/systemd
|
||||||
|
/modules/systemd/systemd-tools
|
||||||
|
/modules/systemd/cmd/systemd-tools/systemd-tools
|
||||||
|
/modules/systemd-networkd/systemd-networkd
|
||||||
|
/modules/systemd-networkd/uplink-tools
|
||||||
|
/modules/systemd-networkd/cmd/uplink-tools/uplink-tools
|
||||||
|
/modules/systemd-resolved/systemd-resolved
|
||||||
|
/modules/systemd-resolved/resolver-tools
|
||||||
|
/modules/systemd-resolved/cmd/resolver-tools/resolver-tools
|
||||||
|
/modules/telegram/telegram
|
||||||
|
/modules/telegram/cmd/telegram/telegram
|
||||||
|
/modules/time-sync/time-sync
|
||||||
|
/modules/time-sync/time-sync-tools
|
||||||
|
/modules/time-sync/cmd/time-sync-tools/time-sync-tools
|
||||||
|
/modules/triggerhappy/triggerhappy
|
||||||
|
/modules/triggerhappy/triggerhappy-tools
|
||||||
|
/modules/triggerhappy/cmd/triggerhappy-tools/triggerhappy-tools
|
||||||
|
/modules/umami/umami
|
||||||
|
/modules/unifi/unifi
|
||||||
|
/modules/website/website
|
||||||
|
/modules/xclip/xclip
|
||||||
|
/modules/xclip/xclip-tools
|
||||||
|
/modules/xclip/cmd/xclip-tools/xclip-tools
|
||||||
|
/modules/xdg/xdg
|
||||||
|
/modules/xdg/xdg-tools
|
||||||
|
/modules/xdg/cmd/xdg-tools/xdg-tools
|
||||||
|
/modules/xorg/xorg
|
||||||
|
/modules/xorg/cmd/xorg/xorg
|
||||||
|
/modules/xterm/xterm
|
||||||
|
/modules/xterm/xterm-tools
|
||||||
|
/modules/xterm/cmd/xterm-tools/xterm-tools
|
||||||
|
/modules/zfs/zfs
|
||||||
|
/modules/zfs/zfs-tools
|
||||||
|
/modules/zfs/cmd/zfs-tools/zfs-tools
|
||||||
|
/modules/zsh/zsh
|
||||||
|
/modules/zsh-autosuggestions/zsh-autosuggestions
|
||||||
|
/modules/zsh-syntax-highlighting/zsh-syntax-highlighting
|
||||||
|
|||||||
Binary file not shown.
@@ -80,12 +80,8 @@ func (m *Module) Puller(steps []StepTool, source buildstep.AskSource) *buildstep
|
|||||||
source = buildstep.SeatAsks{Language: delivery.AskGo}
|
source = buildstep.SeatAsks{Language: delivery.AskGo}
|
||||||
}
|
}
|
||||||
capacity := m.Capacity
|
capacity := m.Capacity
|
||||||
// The capacity's own change wakes the puller (issue 1887): the runtime's watch of the config value as mesh-config
|
|
||||||
// resolved it; the settings file is read only when that watch cannot start. A test's Capacity is its own.
|
|
||||||
var watch func(string, func(stdio.ProvisionChange) error) error
|
|
||||||
if capacity == nil {
|
if capacity == nil {
|
||||||
capacity = buildstep.NodeCapacity(m.Settings, buildstep.DefaultCapacities, m.Node, m.Say)
|
capacity = buildstep.NodeCapacity(m.Settings, buildstep.DefaultCapacities, m.Node, m.Say)
|
||||||
watch = buildstep.WatchConfigServed
|
|
||||||
}
|
}
|
||||||
ask := m.askSteps(steps)
|
ask := m.askSteps(steps)
|
||||||
var load func(string) ([]byte, error)
|
var load func(string) ([]byte, error)
|
||||||
@@ -96,7 +92,7 @@ func (m *Module) Puller(steps []StepTool, source buildstep.AskSource) *buildstep
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return &buildstep.Puller{Source: source, Language: delivery.AskGo, Node: m.Node, Put: m.Steps, Load: load,
|
return &buildstep.Puller{Source: source, Language: delivery.AskGo, Node: m.Node, Put: m.Steps, Load: load,
|
||||||
Capacity: capacity, WatchConfig: watch, Logf: m.Say,
|
Capacity: capacity, Logf: m.Say,
|
||||||
Run: func(ctx context.Context, a delivery.StepAsk) (any, error) {
|
Run: func(ctx context.Context, a delivery.StepAsk) (any, error) {
|
||||||
if err := m.Check(ctx); err != nil {
|
if err := m.Check(ctx); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
@@ -0,0 +1,90 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
// Stray build output (issue 1921, try speed S13): the lint step fails a change that adds an executable (ELF or Mach-O) or
|
||||||
|
// a file over MaxAddedFile, naming each, unless the repository's .binaries-allowed lists its path. Build binaries were
|
||||||
|
// committed three times in one night; the forge keeps them for ever.
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"git.novox.be/novox/mesh-sdk/go/forgekey"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MaxAddedFile is the largest file a change may add without .binaries-allowed naming it.
|
||||||
|
const MaxAddedFile = 5 << 20
|
||||||
|
|
||||||
|
// BinariesAllowed is the file in a repository's root listing, one path per line ("#" starts a comment), the executables
|
||||||
|
// and large files it keeps on purpose.
|
||||||
|
const BinariesAllowed = ".binaries-allowed"
|
||||||
|
|
||||||
|
// executableMagic are the first bytes of an ELF and of each Mach-O form (32 and 64 bits, both byte orders, universal).
|
||||||
|
var executableMagic = [][]byte{[]byte("\x7fELF"), {0xfe, 0xed, 0xfa, 0xce}, {0xfe, 0xed, 0xfa, 0xcf}, {0xce, 0xfa, 0xed, 0xfe},
|
||||||
|
{0xcf, 0xfa, 0xed, 0xfe}, {0xca, 0xfe, 0xba, 0xbe}}
|
||||||
|
|
||||||
|
// addedBinaries is the lint finding of the files the merge adds to the trunk that are executables or over MaxAddedFile,
|
||||||
|
// nil when there is none. The paths come from git (the merge commit against its first parent, the trunk); a tree with no
|
||||||
|
// merge (no first parent) adds nothing to judge.
|
||||||
|
func (m *Module) addedBinaries(ctx context.Context, run forgekey.Runner, tree string) []Failure {
|
||||||
|
r := run(ctx, []string{"GIT_TERMINAL_PROMPT=0"}, "git", "-C", tree, "diff", "--name-only", "--no-renames", "--diff-filter=A", "HEAD^1", "HEAD")
|
||||||
|
if r.Status != 0 || r.Err != "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
allowed := map[string]bool{}
|
||||||
|
if raw, err := os.ReadFile(filepath.Join(tree, BinariesAllowed)); err == nil {
|
||||||
|
scan := bufio.NewScanner(bytes.NewReader(raw))
|
||||||
|
for scan.Scan() {
|
||||||
|
if l := strings.TrimSpace(scan.Text()); l != "" && !strings.HasPrefix(l, "#") {
|
||||||
|
allowed[filepath.ToSlash(l)] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var found []string
|
||||||
|
for _, p := range strings.Split(r.Stdout, "\n") {
|
||||||
|
if p = strings.TrimSpace(p); p == "" || allowed[p] {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
path := filepath.Join(tree, filepath.FromSlash(p))
|
||||||
|
info, err := os.Lstat(path)
|
||||||
|
if err != nil || !info.Mode().IsRegular() {
|
||||||
|
continue // narrowed away, or not a file
|
||||||
|
}
|
||||||
|
if info.Size() > MaxAddedFile {
|
||||||
|
found = append(found, fmt.Sprintf("%s (%d bytes, over %d)", p, info.Size(), MaxAddedFile))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if isExecutable(path) {
|
||||||
|
found = append(found, p+" (an executable)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(found) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
sort.Strings(found)
|
||||||
|
return []Failure{{Package: ".", Test: "binaries", Step: StepLint,
|
||||||
|
Output: "the change adds build output or a large file (remove it, or list its path in " + BinariesAllowed + "): " + strings.Join(found, ", ")}}
|
||||||
|
}
|
||||||
|
|
||||||
|
func isExecutable(path string) bool {
|
||||||
|
f, err := os.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
head := make([]byte, 4)
|
||||||
|
if n, _ := f.Read(head); n < 4 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for _, magic := range executableMagic {
|
||||||
|
if bytes.Equal(head, magic) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||||
|
"git.novox.be/novox/mesh-sdk/go/forgekey"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Issue 1921, try speed S13: the lint step fails a change that adds an executable (ELF or Mach-O) or a file over 5 MB,
|
||||||
|
// naming it, unless .binaries-allowed in the repository's root lists its path; a file the change does not add is not
|
||||||
|
// judged, and neither is an allowed one.
|
||||||
|
func TestTheLintStepFailsAChangeThatAddsABinaryOrALargeFile(t *testing.T) {
|
||||||
|
m, states, ws, _ := workspaceFor(t, "slow")
|
||||||
|
tree := filepath.Join(m.Work, ws, "tree")
|
||||||
|
write := func(name string, body []byte) {
|
||||||
|
os.MkdirAll(filepath.Dir(filepath.Join(tree, name)), 0o755)
|
||||||
|
if err := os.WriteFile(filepath.Join(tree, name), body, 0o644); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
write("go.mod", []byte("module fixture/slow\n"))
|
||||||
|
elf := append([]byte("\x7fELF"), make([]byte, 64)...)
|
||||||
|
macho := append([]byte{0xcf, 0xfa, 0xed, 0xfe}, make([]byte, 64)...)
|
||||||
|
write("modules/x/cmd/x/x", elf)
|
||||||
|
write("modules/y/y", macho)
|
||||||
|
write("big.tar", make([]byte, 5<<20+1))
|
||||||
|
write("tools/allowed-tool", elf)
|
||||||
|
write("old/binary", elf) // there before the change
|
||||||
|
write("modules/x/doc.md", []byte("text\n"))
|
||||||
|
write(".binaries-allowed", []byte("# kept on purpose\ntools/allowed-tool\n"))
|
||||||
|
inner := m.Run
|
||||||
|
m.Run = func(ctx context.Context, env []string, name string, args ...string) forgekey.Ran {
|
||||||
|
if name == "git" && strings.Contains(strings.Join(args, " "), "--diff-filter=A") {
|
||||||
|
return forgekey.Ran{Stdout: "modules/x/cmd/x/x\nmodules/y/y\nbig.tar\ntools/allowed-tool\nmodules/x/doc.md\n.binaries-allowed\n"}
|
||||||
|
}
|
||||||
|
return inner(ctx, env, name, args...)
|
||||||
|
}
|
||||||
|
res, st := runUnit(t, m, states, ws)
|
||||||
|
if st.State != delivery.StepPassed {
|
||||||
|
t.Fatalf("the step ended %s: %s", st.State, st.Reason)
|
||||||
|
}
|
||||||
|
var found []Failure
|
||||||
|
for _, f := range res.Failed {
|
||||||
|
if f.Test == "binaries" {
|
||||||
|
found = append(found, f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if res.Passed || len(found) != 1 || found[0].Step != StepLint {
|
||||||
|
t.Fatalf("failed = %+v, want one lint finding of the binaries", res.Failed)
|
||||||
|
}
|
||||||
|
out := found[0].Output
|
||||||
|
for _, want := range []string{"modules/x/cmd/x/x", "modules/y/y", "big.tar"} {
|
||||||
|
if !strings.Contains(out, want) {
|
||||||
|
t.Errorf("the finding does not name %s: %s", want, out)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, not := range []string{"allowed-tool", "old/binary", "doc.md"} {
|
||||||
|
if strings.Contains(out, not) {
|
||||||
|
t.Errorf("the finding names %s: %s", not, out)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"slices"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 1921/S11 (the lead's decision): a check compiles and runs with CGO_ENABLED=0, as a production build does, so it tests
|
||||||
|
// what ships and the warm cache matches; the lint's go vet, a compile alone, writes the shared cache, while a go test that
|
||||||
|
// runs a test binary reads it beneath the ask's own layer.
|
||||||
|
func TestACheckRunsWithoutCgoAndOnlyItsCompileWritesTheSharedCache(t *testing.T) {
|
||||||
|
m, states, ws, calls := workspaceFor(t, "failing")
|
||||||
|
var seen []shutOff
|
||||||
|
m.Run = throughBubblewrap(t, m.Run, &seen)
|
||||||
|
if _, st := runUnit(t, m, states, ws); st.State != delivery.StepPassed {
|
||||||
|
t.Fatalf("the step ended %s: %s", st.State, st.Reason)
|
||||||
|
}
|
||||||
|
for _, c := range *calls {
|
||||||
|
if strings.HasPrefix(c.line, "go ") && !slices.Contains(c.env, "CGO_ENABLED=0") {
|
||||||
|
t.Errorf("%s ran without CGO_ENABLED=0: %v", c.line, c.env)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
vets, tests := 0, 0
|
||||||
|
for _, s := range seen {
|
||||||
|
switch {
|
||||||
|
case strings.Contains(s.command, " vet "):
|
||||||
|
vets++
|
||||||
|
checkShutOff(t, m, s, true) // the shared caches bound read-write
|
||||||
|
case strings.Contains(s.command, " test "):
|
||||||
|
tests++
|
||||||
|
checkShutOff(t, m, s, false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if vets == 0 || tests == 0 {
|
||||||
|
t.Errorf("%d vets and %d tests ran shut off, want both", vets, tests)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -49,6 +49,51 @@ type FetchResult struct {
|
|||||||
// Beside are the sibling repositories placed as plain directories under the workspace's beside directory, which
|
// Beside are the sibling repositories placed as plain directories under the workspace's beside directory, which
|
||||||
// build_go_test hands to the tests as MESH_CHECK_BESIDE (ADR 0484 clause 4).
|
// build_go_test hands to the tests as MESH_CHECK_BESIDE (ADR 0484 clause 4).
|
||||||
Beside []Sibling `json:"beside,omitempty"`
|
Beside []Sibling `json:"beside,omitempty"`
|
||||||
|
// Took is how long each phase of the fetch ran, in seconds (issue 1921, the try speed-up's first measurement): what
|
||||||
|
// the checker reports as one "clone" time, split.
|
||||||
|
Took *FetchTook `json:"took,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// FetchTook is the time of each phase of a fetch, in seconds; its phases add up to Total. Mirror is bringing the
|
||||||
|
// repository's mirror to the commit, Fetch the fetch and checkout into the tree, Merge the merge preview, Dissociate
|
||||||
|
// the repack that makes the tree independent of the mirror, Beside the placing of the siblings (their own mirror,
|
||||||
|
// fetch and repack), Other the narrowing and the listing of the go.mod directories, and Modules the download and
|
||||||
|
// verify of the Go modules.
|
||||||
|
type FetchTook struct {
|
||||||
|
Mirror float64 `json:"mirror"`
|
||||||
|
Fetch float64 `json:"fetch"`
|
||||||
|
Merge float64 `json:"preview"` // named for the merge preview, apart from the result's merge commit
|
||||||
|
Dissociate float64 `json:"dissociate"`
|
||||||
|
Beside float64 `json:"siblings"`
|
||||||
|
Other float64 `json:"other"`
|
||||||
|
Modules float64 `json:"modules"`
|
||||||
|
Total float64 `json:"total"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// phaseClock stamps the phases of a fetch: each lap is the time since the one before, so the laps add up to the total.
|
||||||
|
type phaseClock struct {
|
||||||
|
now func() time.Time
|
||||||
|
start, since time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
func newPhaseClock(now func() time.Time) *phaseClock {
|
||||||
|
t := now()
|
||||||
|
return &phaseClock{now: now, start: t, since: t}
|
||||||
|
}
|
||||||
|
|
||||||
|
// lap adds the time since the last lap to *into.
|
||||||
|
func (c *phaseClock) lap(into *float64) {
|
||||||
|
t := c.now()
|
||||||
|
*into += t.Sub(c.since).Seconds()
|
||||||
|
c.since = t
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *phaseClock) total() float64 { return c.since.Sub(c.start).Seconds() }
|
||||||
|
|
||||||
|
// String is the one log line a fetch says of its phases.
|
||||||
|
func (t FetchTook) String() string {
|
||||||
|
return fmt.Sprintf("mirror %.1fs, fetch %.1fs, merge %.1fs, dissociate %.1fs, beside %.1fs, other %.1fs, modules %.1fs, total %.1fs",
|
||||||
|
t.Mirror, t.Fetch, t.Merge, t.Dissociate, t.Beside, t.Other, t.Modules, t.Total)
|
||||||
}
|
}
|
||||||
|
|
||||||
// fetchTool is the fetch step tool.
|
// fetchTool is the fetch step tool.
|
||||||
@@ -109,43 +154,77 @@ func (m *Module) fetch(ctx context.Context, args map[string]any) (res any, err e
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (m *Module) fetchInto(ctx context.Context, req buildstep.FetchRequest, id string, siblings []siblingArg) (any, error) {
|
func (m *Module) fetchInto(ctx context.Context, req buildstep.FetchRequest, id string, siblings []siblingArg) (any, error) {
|
||||||
|
res, err := m.fetchPhases(ctx, req, id, siblings)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if m.Say != nil && res.Took != nil {
|
||||||
|
m.Say("the fetch of %s at %s took %s", req.Repository, short(req.Commit), res.Took)
|
||||||
|
}
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func short(commit string) string {
|
||||||
|
if len(commit) > 12 {
|
||||||
|
return commit[:12]
|
||||||
|
}
|
||||||
|
return commit
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Module) fetchPhases(ctx context.Context, req buildstep.FetchRequest, id string, siblings []siblingArg) (FetchResult, error) {
|
||||||
repository, commit, ref, base := req.Repository, req.Commit, req.Ref, req.Base
|
repository, commit, ref, base := req.Repository, req.Commit, req.Ref, req.Base
|
||||||
var err error
|
var err error
|
||||||
|
took := &FetchTook{}
|
||||||
|
clock := newPhaseClock(m.now)
|
||||||
|
defer func() { took.Total = clock.total() }()
|
||||||
root := filepath.Join(m.Work, id)
|
root := filepath.Join(m.Work, id)
|
||||||
tree := filepath.Join(root, "tree")
|
tree := filepath.Join(root, "tree")
|
||||||
if err := os.MkdirAll(tree, 0o700); err != nil {
|
if err := os.MkdirAll(tree, 0o700); err != nil {
|
||||||
return nil, err
|
return FetchResult{}, err
|
||||||
}
|
}
|
||||||
remote := m.remote(repository)
|
remote := m.remote(repository)
|
||||||
if err := buildstep.FetchCommit(ctx, m.Run, tree, remote, commit, ref, m.fetchOptions(ctx, repository, commit, ref)...); err != nil {
|
opts := m.fetchOptions(ctx, repository, commit, ref)
|
||||||
return nil, err
|
mergeOpts := m.FetchOptions
|
||||||
}
|
|
||||||
res := FetchResult{Workspace: id, Commit: commit}
|
|
||||||
if base != "" {
|
if base != "" {
|
||||||
if err := m.merge(ctx, &res, tree, remote, base, commit); err != nil {
|
// The mirror is brought to the trunk commit too, so the merge preview deepens the tree from it and asks the forge
|
||||||
return nil, err
|
// for no history the mirror holds (issue 1921, try speed S6).
|
||||||
|
mergeOpts = m.fetchOptions(ctx, repository, base, "")
|
||||||
|
}
|
||||||
|
clock.lap(&took.Mirror)
|
||||||
|
if err := buildstep.FetchCommit(ctx, m.Run, tree, remote, commit, ref, opts...); err != nil {
|
||||||
|
return FetchResult{}, err
|
||||||
|
}
|
||||||
|
clock.lap(&took.Fetch)
|
||||||
|
res := FetchResult{Workspace: id, Commit: commit, Took: took}
|
||||||
|
if base != "" {
|
||||||
|
if err := m.merge(ctx, &res, tree, remote, base, commit, mergeOpts); err != nil {
|
||||||
|
return FetchResult{}, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
clock.lap(&took.Merge)
|
||||||
if err := m.dissociate(ctx, tree); err != nil {
|
if err := m.dissociate(ctx, tree); err != nil {
|
||||||
return nil, err
|
return FetchResult{}, err
|
||||||
}
|
}
|
||||||
|
clock.lap(&took.Dissociate)
|
||||||
if res.Beside, err = m.placeSiblings(ctx, root, siblings); err != nil {
|
if res.Beside, err = m.placeSiblings(ctx, root, siblings); err != nil {
|
||||||
return nil, err
|
return FetchResult{}, err
|
||||||
}
|
}
|
||||||
|
clock.lap(&took.Beside)
|
||||||
if (base == "" || res.Merge != "") && len(req.Paths) > 0 {
|
if (base == "" || res.Merge != "") && len(req.Paths) > 0 {
|
||||||
if err := buildstep.Narrow(tree, req); err != nil {
|
if err := buildstep.Narrow(tree, req); err != nil {
|
||||||
return nil, err
|
return FetchResult{}, err
|
||||||
}
|
}
|
||||||
// Said beside the tree, so the steps that list the tree's go.mod files leave out those narrowed away.
|
// Said beside the tree, so the steps that list the tree's go.mod files leave out those narrowed away.
|
||||||
if err := os.WriteFile(filepath.Join(root, narrowedMark), nil, 0o600); err != nil {
|
if err := os.WriteFile(filepath.Join(root, narrowedMark), nil, 0o600); err != nil {
|
||||||
return nil, err
|
return FetchResult{}, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
dirs, err := m.goModDirs(ctx, tree)
|
dirs, err := m.goModDirs(ctx, tree)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return FetchResult{}, err
|
||||||
}
|
}
|
||||||
res.Modules = dirs
|
res.Modules = dirs
|
||||||
|
clock.lap(&took.Other)
|
||||||
if base != "" && res.Merge == "" {
|
if base != "" && res.Merge == "" {
|
||||||
return res, nil // a conflict: the tree is at the trunk, nothing of the head is built or tested
|
return res, nil // a conflict: the tree is at the trunk, nothing of the head is built or tested
|
||||||
}
|
}
|
||||||
@@ -168,9 +247,10 @@ func (m *Module) fetchInto(ctx context.Context, req buildstep.FetchRequest, id s
|
|||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
|
clock.lap(&took.Modules)
|
||||||
for _, err := range errs {
|
for _, err := range errs {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return FetchResult{}, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return res, nil
|
return res, nil
|
||||||
@@ -293,6 +373,7 @@ func (m *Module) goEnv() []string {
|
|||||||
modcache = m.GoModCache
|
modcache = m.GoModCache
|
||||||
}
|
}
|
||||||
return []string{
|
return []string{
|
||||||
|
"CGO_ENABLED=0", // as a production build compiles (build.go), so a check tests what ships (issue 1921, S11)
|
||||||
"GOTOOLCHAIN=local",
|
"GOTOOLCHAIN=local",
|
||||||
"GOFLAGS=-mod=mod",
|
"GOFLAGS=-mod=mod",
|
||||||
"GOMODCACHE=" + modcache,
|
"GOMODCACHE=" + modcache,
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package main
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -302,3 +303,61 @@ func TestWorkAndCacheAreTheDirectoriesTheManifestPlacesOutsideTheHome(t *testing
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 1921/S1: the fetch says how long each phase ran (mirror, fetch, merge, dissociate, beside, other, modules), and the
|
||||||
|
// phases add up to its total, so a try's "clone" time is split into its parts.
|
||||||
|
func TestTheFetchSaysTheTimeOfEachPhaseAndTheyAddUpToTheTotal(t *testing.T) {
|
||||||
|
var mu sync.Mutex
|
||||||
|
now := time.Date(2026, 10, 25, 9, 0, 0, 0, time.UTC)
|
||||||
|
advance := func(s int) {
|
||||||
|
mu.Lock()
|
||||||
|
now = now.Add(time.Duration(s) * time.Second)
|
||||||
|
mu.Unlock()
|
||||||
|
}
|
||||||
|
f := &fakeStep{gomods: "go.mod\n", files: map[string]string{"go.mod": "module x\n", "go.sum": ""}, rule: func(line string) (forgekey.Ran, bool) {
|
||||||
|
switch {
|
||||||
|
case strings.Contains(line, "/mirror/") && strings.Contains(line, " fetch "):
|
||||||
|
advance(1)
|
||||||
|
case strings.Contains(line, " fetch --no-tags --depth 1 "):
|
||||||
|
advance(2)
|
||||||
|
case strings.Contains(line, " merge --no-ff "):
|
||||||
|
advance(3)
|
||||||
|
case strings.Contains(line, " repack "):
|
||||||
|
advance(4)
|
||||||
|
case strings.HasSuffix(line, " mod download"):
|
||||||
|
advance(5)
|
||||||
|
}
|
||||||
|
return forgekey.Ran{}, false
|
||||||
|
}}
|
||||||
|
m, states := fetchModule(t, f)
|
||||||
|
said := []string{}
|
||||||
|
m.Say = func(format string, a ...any) { said = append(said, fmt.Sprintf(format, a...)) }
|
||||||
|
m.Now = func() time.Time { mu.Lock(); defer mu.Unlock(); return now }
|
||||||
|
args := map[string]any{"repository": "novox/mesh-sdk", "commit": commitSHA, "base": strings.Repeat("b", 40)}
|
||||||
|
if _, err := fetchTool(t, m)(args); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
st := states.waitEnd(t)
|
||||||
|
if st.State != delivery.StepPassed {
|
||||||
|
t.Fatalf("the step ended %s: %s", st.State, st.Reason)
|
||||||
|
}
|
||||||
|
var res FetchResult
|
||||||
|
if err := json.Unmarshal(st.Result, &res); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if res.Took == nil {
|
||||||
|
t.Fatalf("the result says no phase times: %s", st.Result)
|
||||||
|
}
|
||||||
|
got := *res.Took
|
||||||
|
// the mirror is brought to the head and to the base (1 s each), and the preview deepens from it (1 s) before merging
|
||||||
|
want := FetchTook{Mirror: 2, Fetch: 2, Merge: 4, Dissociate: 4, Modules: 5, Total: 17}
|
||||||
|
if got != want {
|
||||||
|
t.Errorf("phases = %+v, want %+v", got, want)
|
||||||
|
}
|
||||||
|
if sum := got.Mirror + got.Fetch + got.Merge + got.Dissociate + got.Beside + got.Other + got.Modules; sum != got.Total {
|
||||||
|
t.Errorf("the phases add up to %v, not the total %v", sum, got.Total)
|
||||||
|
}
|
||||||
|
if !strings.Contains(strings.Join(said, "\n"), "dissociate 4.0s") {
|
||||||
|
t.Errorf("no log line names the phases: %q", said)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -10,8 +10,10 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.novox.be/novox/mesh-sdk/go/buildstep"
|
"git.novox.be/novox/mesh-sdk/go/buildstep"
|
||||||
@@ -62,34 +64,99 @@ func (m *Module) integrationHere(ctx context.Context, workspace, tree string, pa
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
postgres, stopStore, err := m.startStore(ctx, run)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("the integration level's store: %w", err)
|
|
||||||
}
|
|
||||||
defer stopStore()
|
|
||||||
nats, stopBus, err := m.startBus(ctx, filepath.Join(run, "nats"))
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("the integration level's bus: %w", err)
|
|
||||||
}
|
|
||||||
defer stopBus()
|
|
||||||
env := append([]string{"MESH_TEST_POSTGRES=" + postgres, "MESH_TEST_NATS=" + nats}, m.besideEnv(workspace)...)
|
|
||||||
// Compiling counts as setup: the tests are built once, here, with the addresses a TestMain may read, and the run
|
|
||||||
// below finds them built. Whether they compile is for the run to say; its failure is not read here.
|
|
||||||
// Only the directories the selected packages lie in compile (ADR 0536 §3); no selection is every directory.
|
// Only the directories the selected packages lie in compile (ADR 0536 §3); no selection is every directory.
|
||||||
byDir, err := packagesByDir(tree, dirs, packages)
|
byDir, err := packagesByDir(tree, dirs, packages)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
// The tests the tags add, not the unit tests again (issue 1921, try speed S7): only the packages holding one compile
|
||||||
|
// and run, with a -run pattern naming them. A try that names its own pattern runs it as asked.
|
||||||
|
var runByDir map[string]string
|
||||||
|
if pattern == "" {
|
||||||
|
tagged, err := taggedTests(tree, dirs, packages, integrationTags)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(tagged) == 0 {
|
||||||
|
res := &TestResult{Level: LevelIntegration, Packages: []PackageResult{}}
|
||||||
|
res.judge(m.integrationBounds())
|
||||||
|
return res, nil // no test the tags add: nothing to start or run
|
||||||
|
}
|
||||||
|
byDir, runByDir, packages = map[string][]string{}, map[string]string{}, nil
|
||||||
|
for d, tr := range tagged {
|
||||||
|
byDir[d], runByDir[d] = tr.packages, tr.pattern
|
||||||
|
packages = append(packages, tr.packages...)
|
||||||
|
}
|
||||||
|
sort.Strings(packages)
|
||||||
|
}
|
||||||
|
// The store, the bus and the compile start together (issue 1921, try speed S5); the setup is the longest of them.
|
||||||
|
// The compile runs no test binary (-exec true), so it needs neither server: whether the tests compile is for the run
|
||||||
|
// to say, and its failure is not read here; the run below finds them built.
|
||||||
|
var (
|
||||||
|
wg sync.WaitGroup
|
||||||
|
postgres, nats string
|
||||||
|
stopStore, stopBus func()
|
||||||
|
storeErr, busErr error
|
||||||
|
crash any
|
||||||
|
crashMu sync.Mutex
|
||||||
|
)
|
||||||
|
guard := func() {
|
||||||
|
if p := recover(); p != nil {
|
||||||
|
crashMu.Lock()
|
||||||
|
crash = p
|
||||||
|
crashMu.Unlock()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
wg.Add(2)
|
||||||
|
go func() { defer wg.Done(); defer guard(); postgres, stopStore, storeErr = m.startStore(ctx, run) }()
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
defer guard()
|
||||||
|
nats, stopBus, busErr = m.startBus(ctx, filepath.Join(run, "nats"))
|
||||||
|
}()
|
||||||
|
slots := m.slots(workspace) // shared with the ask's unit level (issue 1921, try speed S10)
|
||||||
|
var slotErr error
|
||||||
for _, d := range dirs {
|
for _, d := range dirs {
|
||||||
if len(byDir[d]) == 0 {
|
if len(byDir[d]) == 0 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
m.Run(ctx, append(m.goEnvFor(filepath.Join(tree, d)), env...), "go", append([]string{"-C", filepath.Join(tree, d), "test", "-tags", strings.Join(integrationTags, ","), "-run", "^$", "-count=1"}, byDir[d]...)...)
|
release, err := slots.acquire(ctx)
|
||||||
|
if err != nil {
|
||||||
|
slotErr = err
|
||||||
|
break
|
||||||
|
}
|
||||||
|
wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
defer release()
|
||||||
|
defer guard()
|
||||||
|
m.Run(ctx, m.goEnvFor(filepath.Join(tree, d)), "go", append([]string{"-C", filepath.Join(tree, d), "test", "-tags", strings.Join(integrationTags, ","), "-exec", "true", "-count=1"}, byDir[d]...)...)
|
||||||
|
}()
|
||||||
}
|
}
|
||||||
|
wg.Wait()
|
||||||
|
if stopStore != nil {
|
||||||
|
defer stopStore()
|
||||||
|
}
|
||||||
|
if stopBus != nil {
|
||||||
|
defer stopBus()
|
||||||
|
}
|
||||||
|
if crash != nil {
|
||||||
|
panic(crash)
|
||||||
|
}
|
||||||
|
if slotErr != nil {
|
||||||
|
return nil, slotErr
|
||||||
|
}
|
||||||
|
if storeErr != nil {
|
||||||
|
return nil, fmt.Errorf("the integration level's store: %w", storeErr)
|
||||||
|
}
|
||||||
|
if busErr != nil {
|
||||||
|
return nil, fmt.Errorf("the integration level's bus: %w", busErr)
|
||||||
|
}
|
||||||
|
env := append([]string{"MESH_TEST_POSTGRES=" + postgres, "MESH_TEST_NATS=" + nats}, m.besideEnv(workspace)...)
|
||||||
setup := m.clock().Sub(began)
|
setup := m.clock().Sub(began)
|
||||||
|
|
||||||
return m.runLevel(ctx, tree, levelOpts{
|
return m.runLevel(ctx, tree, levelOpts{
|
||||||
workspace: workspace, run: pattern,
|
workspace: workspace, run: pattern, runByDir: runByDir,
|
||||||
level: LevelIntegration, tags: integrationTags, packages: packages, bounds: m.integrationBounds(),
|
level: LevelIntegration, tags: integrationTags, packages: packages, bounds: m.integrationBounds(),
|
||||||
env: env,
|
env: env,
|
||||||
setup: setup, wall: true,
|
setup: setup, wall: true,
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
@@ -35,6 +36,8 @@ type integFake struct {
|
|||||||
// command runs, with its line.
|
// command runs, with its line.
|
||||||
gomods string
|
gomods string
|
||||||
onTest func(line string)
|
onTest func(line string)
|
||||||
|
// onCompile is called, unlocked, as each compile of the setup runs (issue 1921, try speed S5).
|
||||||
|
onCompile func(line string)
|
||||||
}
|
}
|
||||||
|
|
||||||
func newIntegFake(t *testing.T, captured string) *integFake {
|
func newIntegFake(t *testing.T, captured string) *integFake {
|
||||||
@@ -71,7 +74,10 @@ func (f *integFake) run(ctx context.Context, env []string, name string, args ...
|
|||||||
return forgekey.Ran{Stdout: f.gomods}
|
return forgekey.Ran{Stdout: f.gomods}
|
||||||
}
|
}
|
||||||
return forgekey.Ran{Stdout: "go.mod\n"}
|
return forgekey.Ran{Stdout: "go.mod\n"}
|
||||||
case name == "go" && strings.Contains(line, "-run ^$"):
|
case name == "go" && strings.Contains(line, "-exec true"):
|
||||||
|
if f.onCompile != nil {
|
||||||
|
f.onCompile(line)
|
||||||
|
}
|
||||||
f.tick("compile")
|
f.tick("compile")
|
||||||
case name == "go":
|
case name == "go":
|
||||||
if f.onTest != nil {
|
if f.onTest != nil {
|
||||||
@@ -123,9 +129,32 @@ func integModule(t *testing.T, f *integFake) (*Module, *keptStates) {
|
|||||||
if err := os.MkdirAll(filepath.Join(m.Work, "ws1", "tree"), 0o755); err != nil {
|
if err := os.MkdirAll(filepath.Join(m.Work, "ws1", "tree"), 0o755); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
// A test the integration tag adds, so the level has a package to run (issue 1921, try speed S7).
|
||||||
|
writeTaggedTest(t, filepath.Join(m.Work, "ws1", "tree"), "fixture/slow")
|
||||||
return m, states
|
return m, states
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// taggedTestFile is the file writeTaggedTest writes: one test the integration tag adds.
|
||||||
|
const taggedTestFile = "level_integration_test.go"
|
||||||
|
|
||||||
|
// writeTaggedTest gives the go.mod directory dir the module path mp and one test the integration tag adds, in its root
|
||||||
|
// package and in its package x.
|
||||||
|
func writeTaggedTest(t *testing.T, dir, mp string) {
|
||||||
|
t.Helper()
|
||||||
|
if err := os.WriteFile(filepath.Join(dir, "go.mod"), []byte("module "+mp+"\n\ngo 1.25.0\n"), 0o644); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
body := "//go:build integration\n\npackage x\n\nimport \"testing\"\n\nfunc TestTakesAWhile(t *testing.T) {}\n"
|
||||||
|
for _, d := range []string{dir, filepath.Join(dir, "x")} {
|
||||||
|
if err := os.MkdirAll(d, 0o755); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := os.WriteFile(filepath.Join(d, taggedTestFile), []byte(body), 0o644); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func runLevelTool(t *testing.T, m *Module, states *keptStates, level string) (TestResult, delivery.Step) {
|
func runLevelTool(t *testing.T, m *Module, states *keptStates, level string) (TestResult, delivery.Step) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
for _, tool := range m.Tools(m.StepTools()) {
|
for _, tool := range m.Tools(m.StepTools()) {
|
||||||
@@ -314,11 +343,11 @@ func TestAStoreThatDoesNotStartEndsTheStepFailedAndLeavesNothing(t *testing.T) {
|
|||||||
if !strings.Contains(f.lines(), " stop") {
|
if !strings.Contains(f.lines(), " stop") {
|
||||||
t.Errorf("the half-started store was not stopped:\n%s", f.lines())
|
t.Errorf("the half-started store was not stopped:\n%s", f.lines())
|
||||||
}
|
}
|
||||||
if f.busDir != "" {
|
if f.busDir != "" && !f.busDown {
|
||||||
t.Errorf("the bus was started after the store failed")
|
t.Errorf("the bus, started beside the store, was not stopped after the store failed")
|
||||||
}
|
}
|
||||||
if entries, _ := os.ReadDir(filepath.Join(m.Work, "ws1")); len(entries) != 1 || entries[0].Name() != "tree" {
|
if left := workspaceLeft(filepath.Join(m.Work, "ws1")); len(left) != 1 || left[0] != "tree" {
|
||||||
t.Errorf("the workspace holds %v, want only its tree", entries)
|
t.Errorf("the workspace holds %v, want only its tree", left)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -377,3 +406,138 @@ func TestTheSocketPathStaysUnderTheLimitForALongRunDirectory(t *testing.T) {
|
|||||||
t.Errorf("the short socket directory %s outlived the store (%v)", sock, err)
|
t.Errorf("the short socket directory %s outlived the store (%v)", sock, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 1921/S5: the setup compiles the go.mod directories side by side, up to the bound, beside the store's and the bus's
|
||||||
|
// start, and compiles without running a test binary, so it needs neither; the setup stays one number.
|
||||||
|
func TestTheSetupCompilesTheDirectoriesAtOnceBesideTheStoreAndTheBus(t *testing.T) {
|
||||||
|
f := newIntegFake(t, "slow")
|
||||||
|
f.gomods = "a/go.mod\nb/go.mod\nc/go.mod\nd/go.mod\ne/go.mod\n"
|
||||||
|
var mu sync.Mutex
|
||||||
|
running, most := 0, 0
|
||||||
|
storeStarting, storeUp, overlap := false, false, false
|
||||||
|
f.onCompile = func(line string) {
|
||||||
|
mu.Lock()
|
||||||
|
running++
|
||||||
|
most = max(most, running)
|
||||||
|
mu.Unlock()
|
||||||
|
time.Sleep(40 * time.Millisecond)
|
||||||
|
mu.Lock()
|
||||||
|
running--
|
||||||
|
mu.Unlock()
|
||||||
|
}
|
||||||
|
m, states := integModule(t, f)
|
||||||
|
m.Parallel = 3
|
||||||
|
tree := filepath.Join(m.Work, "ws1", "tree")
|
||||||
|
for _, d := range []string{"a", "b", "c", "d", "e"} {
|
||||||
|
os.MkdirAll(filepath.Join(tree, d), 0o755)
|
||||||
|
writeTaggedTest(t, filepath.Join(tree, d), "x/"+d)
|
||||||
|
}
|
||||||
|
inner := m.Run
|
||||||
|
m.Run = func(ctx context.Context, env []string, name string, args ...string) forgekey.Ran {
|
||||||
|
line := strings.Join(args, " ")
|
||||||
|
if name == "pg_ctl" && strings.HasSuffix(line, " start") {
|
||||||
|
mu.Lock()
|
||||||
|
storeStarting = true
|
||||||
|
mu.Unlock()
|
||||||
|
time.Sleep(100 * time.Millisecond) // the store takes a while to start
|
||||||
|
defer func() { mu.Lock(); storeUp = true; mu.Unlock() }()
|
||||||
|
}
|
||||||
|
if name == "go" && strings.Contains(line, "-exec true") {
|
||||||
|
time.Sleep(10 * time.Millisecond)
|
||||||
|
mu.Lock()
|
||||||
|
overlap = overlap || (storeStarting && !storeUp)
|
||||||
|
mu.Unlock()
|
||||||
|
for _, e := range env {
|
||||||
|
if strings.HasPrefix(e, "MESH_TEST_") {
|
||||||
|
t.Errorf("the compile was given %s: it runs no test binary", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return inner(ctx, env, name, args...)
|
||||||
|
}
|
||||||
|
res, st := runLevelTool(t, m, states, "integration")
|
||||||
|
if st.State != delivery.StepPassed {
|
||||||
|
t.Fatalf("the step ended %s: %s", st.State, st.Reason)
|
||||||
|
}
|
||||||
|
if most != 3 {
|
||||||
|
t.Errorf("%d compiles ran at once, want the bound of 3", most)
|
||||||
|
}
|
||||||
|
mu.Lock()
|
||||||
|
defer mu.Unlock()
|
||||||
|
if !overlap {
|
||||||
|
t.Errorf("no compile ran while the store was starting:\n%s", f.lines())
|
||||||
|
}
|
||||||
|
if res.Setup < 0 {
|
||||||
|
t.Errorf("setup = %v", res.Setup)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1921/S7: the integration level runs the tests its tags add, not the unit tests again: only the packages holding a
|
||||||
|
// tagged test compile and run, with a -run pattern naming those tests; a tree with no tagged test starts no store.
|
||||||
|
func TestTheIntegrationLevelRunsOnlyTheTestsItsTagsAdd(t *testing.T) {
|
||||||
|
f := newIntegFake(t, "slow")
|
||||||
|
m, states := integModule(t, f)
|
||||||
|
tree := filepath.Join(m.Work, "ws1", "tree")
|
||||||
|
os.RemoveAll(filepath.Join(tree, "x"))
|
||||||
|
os.Remove(filepath.Join(tree, taggedTestFile))
|
||||||
|
for name, body := range map[string]string{
|
||||||
|
"go.mod": "module fixture/slow\n",
|
||||||
|
"unit/u_test.go": "package unit\n\nimport \"testing\"\n\nfunc TestUnit(t *testing.T) {}\n",
|
||||||
|
"store/s_test.go": "package store\n\nimport \"testing\"\n\nfunc TestPlain(t *testing.T) {}\n",
|
||||||
|
"store/s_integration_test.go": "//go:build integration\n\npackage store\n\nimport \"testing\"\n\nfunc TestMain(m *testing.M) {}\nfunc TestReal(t *testing.T) {}\nfunc Testable() {}\nfunc helper() {}\n",
|
||||||
|
"store/fake_test.go": "//go:build !integration\n\npackage store\n\nimport \"testing\"\n\nfunc TestFake(t *testing.T) {}\n",
|
||||||
|
"iso/iso_test.go": "//go:build integration && isolation && linux\n\npackage iso\n\nimport \"testing\"\n\nfunc TestShutOff(t *testing.T) {}\n",
|
||||||
|
"plat/p_linux_test.go": "//go:build linux\n\npackage plat\n\nimport \"testing\"\n\nfunc TestLinux(t *testing.T) {}\n",
|
||||||
|
} {
|
||||||
|
os.MkdirAll(filepath.Dir(filepath.Join(tree, name)), 0o755)
|
||||||
|
if err := os.WriteFile(filepath.Join(tree, name), []byte(body), 0o644); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if _, st := runLevelTool(t, m, states, "integration"); st.State != delivery.StepPassed {
|
||||||
|
t.Fatalf("the step ended %s: %s", st.State, st.Reason)
|
||||||
|
}
|
||||||
|
var compiled, ran []string
|
||||||
|
for _, c := range f.calls {
|
||||||
|
switch {
|
||||||
|
case strings.HasPrefix(c.line, "go ") && strings.Contains(c.line, "-exec true"):
|
||||||
|
compiled = append(compiled, c.line)
|
||||||
|
case strings.HasPrefix(c.line, "go ") && strings.Contains(c.line, " -json"):
|
||||||
|
ran = append(ran, c.line)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(ran) != 1 || !strings.Contains(ran[0], "-run ^(TestReal|TestShutOff)$") ||
|
||||||
|
!strings.HasSuffix(ran[0], " fixture/slow/iso fixture/slow/store") {
|
||||||
|
t.Errorf("the run = %q, want the tagged tests of iso and store alone", ran)
|
||||||
|
}
|
||||||
|
if len(compiled) != 1 || !strings.HasSuffix(compiled[0], " fixture/slow/iso fixture/slow/store") {
|
||||||
|
t.Errorf("the compile = %q, want iso and store alone", compiled)
|
||||||
|
}
|
||||||
|
|
||||||
|
g := newIntegFake(t, "slow")
|
||||||
|
m, states = integModule(t, g)
|
||||||
|
tree = filepath.Join(m.Work, "ws1", "tree")
|
||||||
|
os.RemoveAll(filepath.Join(tree, "x"))
|
||||||
|
os.Remove(filepath.Join(tree, taggedTestFile))
|
||||||
|
os.WriteFile(filepath.Join(tree, "u_test.go"), []byte("package x\n\nimport \"testing\"\n\nfunc TestUnit(t *testing.T) {}\n"), 0o644)
|
||||||
|
res, st := runLevelTool(t, m, states, "integration")
|
||||||
|
if st.State != delivery.StepPassed || !res.Passed || !res.NoTests {
|
||||||
|
t.Fatalf("a tree with no tagged test: %s %+v", st.State, res)
|
||||||
|
}
|
||||||
|
if strings.Contains(g.lines(), "pg_ctl") || strings.Contains(g.lines(), "go -C") {
|
||||||
|
t.Errorf("a tree with no tagged test started the store or ran go:\n%s", g.lines())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// workspaceLeft is what a workspace holds besides the directories its ask keeps there (askDirs: its cache layer and its
|
||||||
|
// bound on processes, gone with the workspace; issue 1921, S2 and S10).
|
||||||
|
func workspaceLeft(dir string) []string {
|
||||||
|
var left []string
|
||||||
|
entries, _ := os.ReadDir(dir)
|
||||||
|
for _, e := range entries {
|
||||||
|
if !slices.Contains(askDirs, e.Name()) {
|
||||||
|
left = append(left, e.Name())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return left
|
||||||
|
}
|
||||||
|
|||||||
@@ -29,21 +29,30 @@ func (m *Module) caches() []string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// isolation is how a run step of the workspace is shut off: the workspace (its tree, its siblings, its run directories,
|
// isolation is how a run step of the workspace is shut off: the workspace (its tree, its siblings, its run directories,
|
||||||
// its packed artifacts) read-write, the caches beneath a throwaway layer unless write, and an environment of a PATH and
|
// its packed artifacts) read-write, the caches beneath the ask's own layer unless write, and an environment of a PATH and
|
||||||
// a locale alone (the step's HOME is its own, inside its /tmp).
|
// a locale alone (the step's HOME is its own, inside its /tmp).
|
||||||
func (m *Module) isolation(workspace string, write bool) buildstep.Isolation {
|
func (m *Module) isolation(workspace string, write bool) buildstep.Isolation {
|
||||||
path := os.Getenv("PATH")
|
path := os.Getenv("PATH")
|
||||||
if path == "" {
|
if path == "" {
|
||||||
path = defaultPath
|
path = defaultPath
|
||||||
}
|
}
|
||||||
return buildstep.Isolation{
|
iso := buildstep.Isolation{
|
||||||
Workspace: filepath.Join(m.Work, workspace),
|
Workspace: filepath.Join(m.Work, workspace),
|
||||||
Caches: m.caches(),
|
Caches: m.caches(),
|
||||||
WriteCaches: write,
|
WriteCaches: write,
|
||||||
Env: []string{"PATH=" + path, "LC_ALL=C"},
|
Env: []string{"PATH=" + path, "LC_ALL=C"},
|
||||||
}
|
}
|
||||||
|
if !write {
|
||||||
|
// What one command of the ask compiles is kept for its later commands (the vet, the unit tests, the integration
|
||||||
|
// level's compile), in the workspace and gone with it (issue 1921, try speed S2).
|
||||||
|
iso.CacheLayer = filepath.Join(iso.Workspace, cacheLayerDir)
|
||||||
|
}
|
||||||
|
return iso
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// cacheLayerDir is the directory in a workspace's root where an ask keeps what its commands wrote into the caches.
|
||||||
|
const cacheLayerDir = "cachelayer"
|
||||||
|
|
||||||
// stepRunner is the runner of a run step of the workspace: buildstep.Isolated over m.Run, so every command it runs is
|
// stepRunner is the runner of a run step of the workspace: buildstep.Isolated over m.Run, so every command it runs is
|
||||||
// shut off; write is true for a trunk build alone (the shared caches are written by trunk builds only). Inside is the
|
// shut off; write is true for a trunk build alone (the shared caches are written by trunk builds only). Inside is the
|
||||||
// integration level's own process, already running shut off (level.go): it runs its commands as they are.
|
// integration level's own process, already running shut off (level.go): it runs its commands as they are.
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import (
|
|||||||
type shutOff struct {
|
type shutOff struct {
|
||||||
flags []string
|
flags []string
|
||||||
binds, roBinds, overlays []string
|
binds, roBinds, overlays []string
|
||||||
|
uppers map[string]string // the writable layer each --overlay gives a cache: cache -> upper
|
||||||
env []string
|
env []string
|
||||||
command string
|
command string
|
||||||
chdir string
|
chdir string
|
||||||
@@ -27,7 +28,7 @@ type shutOff struct {
|
|||||||
|
|
||||||
// bwrapArity is how many words each bubblewrap option the SDK writes takes.
|
// bwrapArity is how many words each bubblewrap option the SDK writes takes.
|
||||||
var bwrapArity = map[string]int{"--unshare-all": 0, "--die-with-parent": 0, "--new-session": 0, "--clearenv": 0,
|
var bwrapArity = map[string]int{"--unshare-all": 0, "--die-with-parent": 0, "--new-session": 0, "--clearenv": 0,
|
||||||
"--proc": 1, "--dev": 1, "--tmpfs": 1, "--dir": 1, "--chdir": 1, "--overlay-src": 1, "--tmp-overlay": 1,
|
"--proc": 1, "--dev": 1, "--tmpfs": 1, "--dir": 1, "--chdir": 1, "--overlay-src": 1, "--tmp-overlay": 1, "--overlay": 3,
|
||||||
"--ro-bind": 2, "--ro-bind-try": 2, "--bind": 2, "--symlink": 2, "--setenv": 2}
|
"--ro-bind": 2, "--ro-bind-try": 2, "--bind": 2, "--symlink": 2, "--setenv": 2}
|
||||||
|
|
||||||
// throughBubblewrap is the fake runner of a step's tests: a bubblewrap command is read back (recorded in seen) and its
|
// throughBubblewrap is the fake runner of a step's tests: a bubblewrap command is read back (recorded in seen) and its
|
||||||
@@ -56,6 +57,11 @@ func throughBubblewrap(t *testing.T, run forgekey.Runner, seen *[]shutOff) forge
|
|||||||
s.roBinds = append(s.roBinds, args[i+1])
|
s.roBinds = append(s.roBinds, args[i+1])
|
||||||
case "--overlay-src":
|
case "--overlay-src":
|
||||||
s.overlays = append(s.overlays, args[i+1])
|
s.overlays = append(s.overlays, args[i+1])
|
||||||
|
case "--overlay":
|
||||||
|
if s.uppers == nil {
|
||||||
|
s.uppers = map[string]string{}
|
||||||
|
}
|
||||||
|
s.uppers[args[i+3]] = args[i+1]
|
||||||
case "--setenv":
|
case "--setenv":
|
||||||
s.env = append(s.env, args[i+1]+"="+args[i+2])
|
s.env = append(s.env, args[i+1]+"="+args[i+2])
|
||||||
case "--chdir":
|
case "--chdir":
|
||||||
@@ -105,6 +111,9 @@ func checkShutOff(t *testing.T, m *Module, s shutOff, write bool) {
|
|||||||
t.Errorf("a trunk build's %s does not write the shared cache %s (bound %v, layered %v)", s.command, c, bound, layered)
|
t.Errorf("a trunk build's %s does not write the shared cache %s (bound %v, layered %v)", s.command, c, bound, layered)
|
||||||
case !write && (bound || !layered):
|
case !write && (bound || !layered):
|
||||||
t.Errorf("%s may write the shared cache %s (bound %v, layered %v): only a trunk build does", s.command, c, bound, layered)
|
t.Errorf("%s may write the shared cache %s (bound %v, layered %v): only a trunk build does", s.command, c, bound, layered)
|
||||||
|
case !write && !strings.HasPrefix(s.uppers[c], filepath.Join(m.Work, "ws1", "cachelayer")+"/"):
|
||||||
|
// 1921/S2: what one command of the ask compiles is kept for its later commands, in the workspace
|
||||||
|
t.Errorf("%s writes the cache %s into %q, not the ask's cache layer in its workspace", s.command, c, s.uppers[c])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if want := filepath.Join(m.Work, "ws1"); !slices.Contains(s.binds, want) {
|
if want := filepath.Join(m.Work, "ws1"); !slices.Contains(s.binds, want) {
|
||||||
@@ -131,7 +140,8 @@ func TestTheUnitLevelRunsEveryGoTestShutOff(t *testing.T) {
|
|||||||
t.Fatalf("%d go commands ran, %d of them shut off: %v", ran, len(seen), *calls)
|
t.Fatalf("%d go commands ran, %d of them shut off: %v", ran, len(seen), *calls)
|
||||||
}
|
}
|
||||||
for _, s := range seen {
|
for _, s := range seen {
|
||||||
checkShutOff(t, m, s, false)
|
// the lint's go vet, a compile alone, writes the shared cache (issue 1921, S11): cgo_test.go
|
||||||
|
checkShutOff(t, m, s, strings.Contains(s.command, " vet "))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -41,6 +41,8 @@ type levelSpec struct {
|
|||||||
Run string `json:"run,omitempty"`
|
Run string `json:"run,omitempty"`
|
||||||
Setup time.Duration `json:"setup"`
|
Setup time.Duration `json:"setup"`
|
||||||
Bound time.Duration `json:"bound"`
|
Bound time.Duration `json:"bound"`
|
||||||
|
// Parallel is the ask's bound on processes, which the process shares with the ask's other level (issue 1921, S10).
|
||||||
|
Parallel int `json:"parallel,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// levelAnswer is the one line the process prints: the level's result or the digest, or why it could not run.
|
// levelAnswer is the one line the process prints: the level's result or the digest, or why it could not run.
|
||||||
@@ -83,7 +85,7 @@ func (m *Module) integrationShutOff(ctx context.Context, workspace string, packa
|
|||||||
// workspace read-write, the program alone read-only, the shared caches beneath a throwaway layer, the Node build
|
// workspace read-write, the program alone read-only, the shared caches beneath a throwaway layer, the Node build
|
||||||
// module's toolchains read-only for the integration level.
|
// module's toolchains read-only for the integration level.
|
||||||
func (m *Module) shutOff(ctx context.Context, exe string, spec levelSpec) (levelAnswer, error) {
|
func (m *Module) shutOff(ctx context.Context, exe string, spec levelSpec) (levelAnswer, error) {
|
||||||
spec.Work, spec.Cache = m.Work, m.Cache
|
spec.Work, spec.Cache, spec.Parallel = m.Work, m.Cache, m.parallel()
|
||||||
raw, err := json.Marshal(spec)
|
raw, err := json.Marshal(spec)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return levelAnswer{}, err
|
return levelAnswer{}, err
|
||||||
@@ -165,7 +167,7 @@ func runLevelProcess(args []string) int {
|
|||||||
return say(levelAnswer{Error: fmt.Sprintf("%q is not a workspace id", spec.Workspace)})
|
return say(levelAnswer{Error: fmt.Sprintf("%q is not a workspace id", spec.Workspace)})
|
||||||
}
|
}
|
||||||
m := &Module{Inside: true, Recipe: Current(), Work: spec.Work, Cache: spec.Cache, Home: os.Getenv("HOME"), UnitBound: spec.Bound,
|
m := &Module{Inside: true, Recipe: Current(), Work: spec.Work, Cache: spec.Cache, Home: os.Getenv("HOME"), UnitBound: spec.Bound,
|
||||||
IntegrationBounds: Bounds{Setup: spec.Setup, Run: spec.Bound}, Say: func(string, ...any) {}}
|
IntegrationBounds: Bounds{Setup: spec.Setup, Run: spec.Bound}, Parallel: spec.Parallel, Say: func(string, ...any) {}}
|
||||||
m.Run = StepRunner(m.Home)
|
m.Run = StepRunner(m.Home)
|
||||||
return say(m.answer(context.Background(), spec))
|
return say(m.answer(context.Background(), spec))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"regexp"
|
"regexp"
|
||||||
"slices"
|
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@@ -53,17 +52,19 @@ type TestTime struct {
|
|||||||
// Failure is a test that failed, or a package that failed with no test to name (a build that failed, a TestMain that
|
// Failure is a test that failed, or a package that failed with no test to name (a build that failed, a TestMain that
|
||||||
// panicked); Output is the last lines it wrote, up to outputBytes (issue 1901), and Cause the first line of what the
|
// panicked); Output is the last lines it wrote, up to outputBytes (issue 1901), and Cause the first line of what the
|
||||||
// test reported as its error (t.Error, t.Fatal), without its file and line, when go test said which lines those were.
|
// test reported as its error (t.Error, t.Fatal), without its file and line, when go test said which lines those were.
|
||||||
// **A failure that is not one test's assertion carries its real cause too** (issue 1901, its third meeting): the
|
|
||||||
// "panic:" line (a timeout's with the tests it found running), a "fatal error:" or "signal:" line, a build's first
|
|
||||||
// error, or in words that the test program exited, while which tests ran or after its tests passed. Test then names the
|
|
||||||
// tests that were running when the program stopped. A "PASS" or "ok" line is never a failure's words.
|
|
||||||
type Failure struct {
|
type Failure struct {
|
||||||
Package string `json:"package"`
|
Package string `json:"package"`
|
||||||
Test string `json:"test,omitempty"`
|
Test string `json:"test,omitempty"`
|
||||||
Output string `json:"output,omitempty"`
|
Output string `json:"output,omitempty"`
|
||||||
Cause string `json:"cause,omitempty"`
|
Cause string `json:"cause,omitempty"`
|
||||||
|
// Step is StepLint for a finding of the lint step (gofmt, go vet), which runs beside the unit level and is not one of
|
||||||
|
// the levels (ADR 0542 §2): the checker reports it as the lint step's failure. Empty for a test's failure.
|
||||||
|
Step string `json:"step,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// StepLint is the Step of a lint finding.
|
||||||
|
const StepLint = "lint"
|
||||||
|
|
||||||
// TestResult is what a test step passes with.
|
// TestResult is what a test step passes with.
|
||||||
type TestResult struct {
|
type TestResult struct {
|
||||||
Level string `json:"level"`
|
Level string `json:"level"`
|
||||||
@@ -78,7 +79,7 @@ type TestResult struct {
|
|||||||
Elapsed float64 `json:"elapsed"`
|
Elapsed float64 `json:"elapsed"`
|
||||||
// Setup is the level's setup time in seconds, when it has a setup.
|
// Setup is the level's setup time in seconds, when it has a setup.
|
||||||
Setup float64 `json:"setup,omitempty"`
|
Setup float64 `json:"setup,omitempty"`
|
||||||
// Lint is the unit level's gofmt and go vet time in seconds, before its tests (ADR 0529 §1).
|
// Lint is the unit level's gofmt and go vet time in seconds, run beside its tests (ADR 0529 §1; issue 1921).
|
||||||
Lint float64 `json:"lint,omitempty"`
|
Lint float64 `json:"lint,omitempty"`
|
||||||
// Bound and SetupBound are the bounds the level was judged by, in seconds.
|
// Bound and SetupBound are the bounds the level was judged by, in seconds.
|
||||||
Bound float64 `json:"bound"`
|
Bound float64 `json:"bound"`
|
||||||
@@ -131,18 +132,11 @@ type reader struct {
|
|||||||
causes map[string]string // the first error line of a test, by the same key
|
causes map[string]string // the first error line of a test, by the same key
|
||||||
times []TestTime
|
times []TestTime
|
||||||
failedIn map[string]bool // packages with a failed test
|
failedIn map[string]bool // packages with a failed test
|
||||||
// running is the tests that started and have not ended, by package, in the order they started: when a package
|
|
||||||
// fails with no failed test, these were running when its test program stopped (issue 1901).
|
|
||||||
running map[string][]string
|
|
||||||
// passed says a package's test program printed "PASS" (its tests passed) before it failed; built says its test
|
|
||||||
// program failed to build.
|
|
||||||
passed, built map[string]bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func newReader(res *TestResult, dir string) *reader {
|
func newReader(res *TestResult, dir string) *reader {
|
||||||
return &reader{res: res, dir: dir, pkgs: map[string]*PackageResult{}, builds: map[string][]string{},
|
return &reader{res: res, dir: dir, pkgs: map[string]*PackageResult{}, builds: map[string][]string{},
|
||||||
pkgOut: map[string][]string{}, testOut: map[string][]string{}, causes: map[string]string{}, failedIn: map[string]bool{},
|
pkgOut: map[string][]string{}, testOut: map[string][]string{}, causes: map[string]string{}, failedIn: map[string]bool{}}
|
||||||
running: map[string][]string{}, passed: map[string]bool{}, built: map[string]bool{}}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// read takes go test's output. Lines that are not events are kept for a failure that has nothing else to say.
|
// read takes go test's output. Lines that are not events are kept for a failure that has nothing else to say.
|
||||||
@@ -190,21 +184,12 @@ func (r *reader) event(ev event) {
|
|||||||
p := r.pkg(ev.Package)
|
p := r.pkg(ev.Package)
|
||||||
switch ev.Action {
|
switch ev.Action {
|
||||||
case "output":
|
case "output":
|
||||||
// go test's own summary of a package ("PASS", "FAIL", "ok <pkg>", "FAIL <pkg>") is never a failure's
|
r.pkgOut[ev.Package] = keep(r.pkgOut[ev.Package], ev.Output)
|
||||||
// words: a "PASS" before a "FAIL <pkg>" says only that the tests passed and the program failed after them.
|
|
||||||
switch l := strings.TrimRight(ev.Output, "\r\n"); {
|
|
||||||
case l == "PASS":
|
|
||||||
r.passed[ev.Package] = true
|
|
||||||
case summaryLine(l):
|
|
||||||
default:
|
|
||||||
r.pkgOut[ev.Package] = keep(r.pkgOut[ev.Package], ev.Output)
|
|
||||||
}
|
|
||||||
case "pass":
|
case "pass":
|
||||||
p.Passed, p.Elapsed = true, ev.Elapsed
|
p.Passed, p.Elapsed = true, ev.Elapsed
|
||||||
case "fail":
|
case "fail":
|
||||||
p.Passed, p.Elapsed = false, ev.Elapsed
|
p.Passed, p.Elapsed = false, ev.Elapsed
|
||||||
if ev.FailedBuild != "" {
|
if ev.FailedBuild != "" {
|
||||||
r.built[ev.Package] = true
|
|
||||||
r.pkgOut[ev.Package] = append(r.builds[ev.FailedBuild], r.pkgOut[ev.Package]...)
|
r.pkgOut[ev.Package] = append(r.builds[ev.FailedBuild], r.pkgOut[ev.Package]...)
|
||||||
}
|
}
|
||||||
case "skip":
|
case "skip":
|
||||||
@@ -218,10 +203,7 @@ func (r *reader) event(ev event) {
|
|||||||
if ev.OutputType == "error" && r.causes[key] == "" {
|
if ev.OutputType == "error" && r.causes[key] == "" {
|
||||||
r.causes[key] = errorWords(ev.Output)
|
r.causes[key] = errorWords(ev.Output)
|
||||||
}
|
}
|
||||||
case "run":
|
|
||||||
r.running[ev.Package] = append(r.running[ev.Package], ev.Test)
|
|
||||||
case "pass", "fail", "skip":
|
case "pass", "fail", "skip":
|
||||||
r.running[ev.Package] = slices.DeleteFunc(r.running[ev.Package], func(t string) bool { return t == ev.Test })
|
|
||||||
p := r.pkg(ev.Package)
|
p := r.pkg(ev.Package)
|
||||||
p.Tests++
|
p.Tests++
|
||||||
r.res.Tests++
|
r.res.Tests++
|
||||||
@@ -231,23 +213,16 @@ func (r *reader) event(ev event) {
|
|||||||
r.times = append(r.times, TestTime{Package: ev.Package, Test: ev.Test, Elapsed: ev.Elapsed})
|
r.times = append(r.times, TestTime{Package: ev.Package, Test: ev.Test, Elapsed: ev.Elapsed})
|
||||||
if ev.Action == "fail" {
|
if ev.Action == "fail" {
|
||||||
r.failedIn[ev.Package] = true
|
r.failedIn[ev.Package] = true
|
||||||
cause := r.causes[key]
|
r.res.Failed = append(r.res.Failed, Failure{Package: ev.Package, Test: ev.Test, Output: clip(r.testOut[key], r.causes[key]), Cause: r.causes[key]})
|
||||||
if race := raceCause(r.testOut[key]); race != "" && (cause == "" || strings.HasPrefix(cause, "race detected")) {
|
|
||||||
cause = race // the race itself, not testing's "race detected during execution of test"
|
|
||||||
} else if cause == "" {
|
|
||||||
cause = causeOf(r.testOut[key])
|
|
||||||
}
|
|
||||||
r.res.Failed = append(r.res.Failed, Failure{Package: ev.Package, Test: ev.Test, Output: clip(r.testOut[key], cause), Cause: cause})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// clip joins the last lines of a failure's output that fit outputBytes, each cut to outputLineBytes, and says how many
|
// clip joins the last lines of a failure's output that fit outputBytes, each cut to outputLineBytes, and says how many
|
||||||
// earlier lines it left out. **Its first line is the failure's cause** (issue 1901/AC3, AC4), put ahead when the kept
|
// earlier lines it left out. **When it leaves lines out, its first line is still a real one** (issue 1901/AC3): the failure's
|
||||||
// lines do not start with it, or, with no cause and lines left out, the first non-empty line of the output, then the
|
// cause, or failing that the first non-empty line of the output, then the marker. An older checker takes only the first
|
||||||
// marker. An older checker takes only the first non-empty line of the output, and a marker there would be less than the
|
// non-empty line of the output, and a marker there would be less than the older pair kept.
|
||||||
// older pair kept.
|
|
||||||
func clip(lines []string, cause string) string {
|
func clip(lines []string, cause string) string {
|
||||||
size, from := 0, len(lines)
|
size, from := 0, len(lines)
|
||||||
for from > 0 {
|
for from > 0 {
|
||||||
@@ -258,15 +233,15 @@ func clip(lines []string, cause string) string {
|
|||||||
size += n
|
size += n
|
||||||
from--
|
from--
|
||||||
}
|
}
|
||||||
kept := make([]string, 0, len(lines)-from+2)
|
kept := make([]string, 0, len(lines)-from+1)
|
||||||
head := strings.TrimSpace(cause)
|
|
||||||
for i := 0; head == "" && i < from; i++ {
|
|
||||||
head = strings.TrimSpace(lines[i])
|
|
||||||
}
|
|
||||||
if head != "" && (from > 0 || len(lines) == 0 || strings.TrimSpace(lines[from]) != head) {
|
|
||||||
kept = append(kept, cutLine(head))
|
|
||||||
}
|
|
||||||
if from > 0 {
|
if from > 0 {
|
||||||
|
head := strings.TrimSpace(cause)
|
||||||
|
for i := 0; head == "" && i < from; i++ {
|
||||||
|
head = strings.TrimSpace(lines[i])
|
||||||
|
}
|
||||||
|
if head != "" {
|
||||||
|
kept = append(kept, cutLine(head))
|
||||||
|
}
|
||||||
kept = append(kept, fmt.Sprintf("... (%d earlier lines)", from))
|
kept = append(kept, fmt.Sprintf("... (%d earlier lines)", from))
|
||||||
}
|
}
|
||||||
for _, l := range lines[from:] {
|
for _, l := range lines[from:] {
|
||||||
@@ -275,159 +250,6 @@ func clip(lines []string, cause string) string {
|
|||||||
return strings.Join(kept, "\n")
|
return strings.Join(kept, "\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
// summaryLine says a line is go test's summary of a package, in go's exact forms only: "PASS" or "FAIL" alone,
|
|
||||||
// "ok \t<pkg>\t<time>" and "FAIL\t<pkg>\t<time>" (review round 1: a line of the package's own that starts "ok " is kept).
|
|
||||||
func summaryLine(l string) bool {
|
|
||||||
return l == "PASS" || l == "FAIL" || strings.HasPrefix(l, "ok \t") || strings.HasPrefix(l, "FAIL\t")
|
|
||||||
}
|
|
||||||
|
|
||||||
// causeOf is the cause in a failure's output that is not a test's assertion (issue 1901): a race the race detector
|
|
||||||
// reported (raceCause); else the first "panic:" line, a timeout's with the tests it names as running; else the first
|
|
||||||
// "fatal error:" (the runtime's) or "signal:" line (a test program killed); "" when there is none. It reads every line,
|
|
||||||
// so a long goroutine dump after the panic cannot hide it. **It matches only lines at column 0**, where go prints the
|
|
||||||
// runtime's: a test's indented log line holding the same words is not the cause (review round 1).
|
|
||||||
func causeOf(lines []string) string {
|
|
||||||
if race := raceCause(lines); race != "" {
|
|
||||||
return race
|
|
||||||
}
|
|
||||||
for _, prefix := range []string{"panic: ", "fatal error: ", "signal: "} {
|
|
||||||
for i, l := range lines {
|
|
||||||
l = strings.TrimRight(l, "\r\n") // at column 0: an indented line is a test's own log, not the runtime's
|
|
||||||
if !strings.HasPrefix(l, prefix) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if strings.HasPrefix(l, "panic: test timed out after") {
|
|
||||||
var names []string
|
|
||||||
for _, n := range lines[i+1:] {
|
|
||||||
if !strings.HasPrefix(n, "\t\t") {
|
|
||||||
if strings.TrimSpace(n) == "running tests:" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
names = append(names, strings.TrimSpace(n))
|
|
||||||
}
|
|
||||||
if len(names) > 0 {
|
|
||||||
l += "; running tests: " + strings.Join(names, ", ")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return cutLine(l)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
// raceCause is the first race the race detector reported in a failure's output, in one line (issue 1901: a race after
|
|
||||||
// the tests' "PASS" was worded "PASS"): its access and the frame that made it, and where the goroutine that made it was
|
|
||||||
// created, as "WARNING: DATA RACE: Write at 0x... by goroutine 10 in pkg.F.func1() (f_test.go:13); goroutine created at
|
|
||||||
// pkg.F() (f_test.go:13)". "" when the output holds no race.
|
|
||||||
func raceCause(lines []string) string {
|
|
||||||
at := -1
|
|
||||||
for i, l := range lines {
|
|
||||||
if strings.TrimRight(l, "\r\n") == "WARNING: DATA RACE" { // at column 0, as causeOf
|
|
||||||
at = i
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if at < 0 {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
// frame is the function and the file's base name and line that follow a heading line at i.
|
|
||||||
frame := func(i int) string {
|
|
||||||
var fn, file string
|
|
||||||
for _, l := range lines[i+1:] {
|
|
||||||
if l = strings.TrimSpace(l); l == "" {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if fn == "" {
|
|
||||||
fn = l
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
file = l
|
|
||||||
if j := strings.LastIndexByte(file, '/'); j >= 0 {
|
|
||||||
file = file[j+1:]
|
|
||||||
}
|
|
||||||
if j := strings.Index(file, " +0x"); j >= 0 {
|
|
||||||
file = file[:j]
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if file != "" {
|
|
||||||
return fn + " (" + file + ")"
|
|
||||||
}
|
|
||||||
return fn
|
|
||||||
}
|
|
||||||
words := "WARNING: DATA RACE"
|
|
||||||
for i := at + 1; i < len(lines); i++ {
|
|
||||||
l := strings.TrimSpace(lines[i])
|
|
||||||
if l == "" || strings.HasPrefix(l, "===") {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
words += ": " + strings.TrimSuffix(l, ":")
|
|
||||||
if f := frame(i); f != "" {
|
|
||||||
words += " in " + f
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
for i := at + 1; i < len(lines); i++ {
|
|
||||||
l := strings.TrimSpace(lines[i])
|
|
||||||
if strings.HasPrefix(l, "==================") {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if strings.HasPrefix(l, "Goroutine ") && strings.HasSuffix(l, " created at:") {
|
|
||||||
if f := frame(i); f != "" {
|
|
||||||
words += "; its goroutine created at " + f
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return cutLine(words)
|
|
||||||
}
|
|
||||||
|
|
||||||
// buildCause is the first error line of a failed build: the first line that is not go's "# <package>" heading.
|
|
||||||
func buildCause(lines []string) string {
|
|
||||||
for _, l := range lines {
|
|
||||||
if l = strings.TrimSpace(l); l != "" && !strings.HasPrefix(l, "# ") {
|
|
||||||
return cutLine(l)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
// testNames names the tests that were running, at most three, and how many more.
|
|
||||||
func testNames(tests []string) string {
|
|
||||||
if len(tests) <= 3 {
|
|
||||||
return strings.Join(tests, ", ")
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("%s and %d more", strings.Join(tests[:3], ", "), len(tests)-3)
|
|
||||||
}
|
|
||||||
|
|
||||||
// packageFailure is a package that failed with no failed test to name it (issue 1901): its test program did not build,
|
|
||||||
// panicked, timed out, was killed or exited. It names the tests that were running when the program stopped, keeps
|
|
||||||
// their output with the package's, and says the cause.
|
|
||||||
func (r *reader) packageFailure(name string) Failure {
|
|
||||||
running := r.running[name]
|
|
||||||
var lines []string
|
|
||||||
for _, t := range running {
|
|
||||||
lines = append(lines, r.testOut[name+"\x00"+t]...)
|
|
||||||
}
|
|
||||||
lines = append(lines, r.pkgOut[name]...)
|
|
||||||
cause := causeOf(lines)
|
|
||||||
switch {
|
|
||||||
case cause != "":
|
|
||||||
case r.built[name]:
|
|
||||||
cause = buildCause(lines)
|
|
||||||
case len(running) > 0:
|
|
||||||
cause = "the test program exited while " + testNames(running) + " ran, with no panic: an os.Exit or a signal (go test -json reports no exit status)"
|
|
||||||
case r.passed[name]:
|
|
||||||
cause = "the tests passed, then the test program exited with a failure: an os.Exit or a panic in TestMain or after the tests (go test -json reports no exit status)"
|
|
||||||
}
|
|
||||||
if cause == "" {
|
|
||||||
cause = "the package failed with no test to name and no panic, signal or build error in its output"
|
|
||||||
}
|
|
||||||
return Failure{Package: name, Test: testNames(running), Output: clip(lines, cause), Cause: cause}
|
|
||||||
}
|
|
||||||
|
|
||||||
// cutLine cuts a line to outputLineBytes, on a character boundary.
|
// cutLine cuts a line to outputLineBytes, on a character boundary.
|
||||||
func cutLine(l string) string {
|
func cutLine(l string) string {
|
||||||
if len(l) <= outputLineBytes {
|
if len(l) <= outputLineBytes {
|
||||||
@@ -449,7 +271,7 @@ func (r *reader) finish() {
|
|||||||
for _, name := range r.order {
|
for _, name := range r.order {
|
||||||
p := r.pkgs[name]
|
p := r.pkgs[name]
|
||||||
if !p.Passed && !r.failedIn[name] {
|
if !p.Passed && !r.failedIn[name] {
|
||||||
r.res.Failed = append(r.res.Failed, r.packageFailure(name))
|
r.res.Failed = append(r.res.Failed, Failure{Package: name, Output: clip(r.pkgOut[name], "")})
|
||||||
}
|
}
|
||||||
if !p.NoTests && p.Elapsed > r.res.Elapsed {
|
if !p.NoTests && p.Elapsed > r.res.Elapsed {
|
||||||
r.res.Elapsed = p.Elapsed
|
r.res.Elapsed = p.Elapsed
|
||||||
|
|||||||
@@ -60,17 +60,23 @@ func lintable(dir string, each func(path string) error) error {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// unformatted are the Go files of a directory gofmt would change, relative to the tree, and the build tags they name.
|
// unformatted are the Go files of a directory gofmt would change, relative to the tree, the build tags the directory's
|
||||||
func unformatted(tree, d string) (files, tags []string, err error) {
|
// files name, and whether a file is left out by one of them (a negated tag, //go:build !integration). Only files whose
|
||||||
|
// directory (relative to the go.mod directory, "." for its own) is in only are checked for gofmt; nil checks every file.
|
||||||
|
func unformatted(tree, d string, only map[string]bool) (files, tags []string, negated bool, err error) {
|
||||||
seen := map[string]bool{}
|
seen := map[string]bool{}
|
||||||
err = lintable(filepath.Join(tree, d), func(path string) error {
|
base := filepath.Join(tree, d)
|
||||||
|
err = lintable(base, func(path string) error {
|
||||||
src, err := os.ReadFile(path)
|
src, err := os.ReadFile(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if out, ferr := format.Source(src); ferr != nil || !bytes.Equal(out, src) {
|
pkgDir, _ := filepath.Rel(base, filepath.Dir(path))
|
||||||
rel, _ := filepath.Rel(tree, path)
|
if only == nil || only[filepath.ToSlash(pkgDir)] {
|
||||||
files = append(files, filepath.ToSlash(rel))
|
if out, ferr := format.Source(src); ferr != nil || !bytes.Equal(out, src) {
|
||||||
|
rel, _ := filepath.Rel(tree, path)
|
||||||
|
files = append(files, filepath.ToSlash(rel))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
scan := bufio.NewScanner(bytes.NewReader(src))
|
scan := bufio.NewScanner(bytes.NewReader(src))
|
||||||
for scan.Scan() {
|
for scan.Scan() {
|
||||||
@@ -79,8 +85,15 @@ func unformatted(tree, d string) (files, tags []string, err error) {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
if expr, ok := strings.CutPrefix(line, "//go:build "); ok {
|
if expr, ok := strings.CutPrefix(line, "//go:build "); ok {
|
||||||
for _, tag := range buildTag.FindAllString(expr, -1) {
|
for _, at := range buildTag.FindAllStringIndex(expr, -1) {
|
||||||
if !notATag[tag] && !strings.HasPrefix(tag, "go1") && !seen[tag] {
|
tag := expr[at[0]:at[1]]
|
||||||
|
if notATag[tag] || strings.HasPrefix(tag, "go1") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if before := strings.TrimRight(expr[:at[0]], " ("); strings.HasSuffix(before, "!") {
|
||||||
|
negated = true
|
||||||
|
}
|
||||||
|
if !seen[tag] {
|
||||||
seen[tag] = true
|
seen[tag] = true
|
||||||
tags = append(tags, tag)
|
tags = append(tags, tag)
|
||||||
}
|
}
|
||||||
@@ -91,62 +104,123 @@ func unformatted(tree, d string) (files, tags []string, err error) {
|
|||||||
})
|
})
|
||||||
sort.Strings(files)
|
sort.Strings(files)
|
||||||
sort.Strings(tags)
|
sort.Strings(tags)
|
||||||
return files, tags, err
|
return files, tags, negated, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// lint is gofmt and go vet of one go.mod directory d of the tree, run with run in the directory's go environment; a
|
// boundOf is a bound of n processes of this process alone.
|
||||||
// failure of either is a Failure of the level naming what failed.
|
func boundOf(n int) func(context.Context) (func(), error) {
|
||||||
func (m *Module) lint(ctx context.Context, run forgekey.Runner, tree, d string, env []string) ([]Failure, error) {
|
slots := make(chan struct{}, n)
|
||||||
files, tags, err := unformatted(tree, d)
|
return func(context.Context) (func(), error) {
|
||||||
|
slots <- struct{}{}
|
||||||
|
return func() { <-slots }, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// lintScope is what of a go.mod directory d the lint covers, from the patterns the level runs there: every package
|
||||||
|
// (./..., and the directories nil) when a pattern is relative or none is given, else the import paths given and the
|
||||||
|
// directories of their files, relative to d.
|
||||||
|
func lintScope(tree, d string, patterns []string) (targets []string, dirs map[string]bool) {
|
||||||
|
if len(patterns) == 0 {
|
||||||
|
return []string{"./..."}, nil
|
||||||
|
}
|
||||||
|
raw, _ := os.ReadFile(filepath.Join(tree, d, "go.mod"))
|
||||||
|
mp := modulePath(string(raw))
|
||||||
|
dirs = map[string]bool{}
|
||||||
|
for _, p := range patterns {
|
||||||
|
if strings.HasPrefix(p, "./") || p == "." {
|
||||||
|
return []string{"./..."}, nil
|
||||||
|
}
|
||||||
|
rel, ok := strings.CutPrefix(p, mp)
|
||||||
|
if mp == "" || !ok || (rel != "" && !strings.HasPrefix(rel, "/")) {
|
||||||
|
return []string{"./..."}, nil // a package of no module here: the whole directory, as before
|
||||||
|
}
|
||||||
|
if rel = strings.TrimPrefix(rel, "/"); rel == "" {
|
||||||
|
rel = "."
|
||||||
|
}
|
||||||
|
dirs[rel] = true
|
||||||
|
targets = append(targets, p)
|
||||||
|
}
|
||||||
|
return targets, dirs
|
||||||
|
}
|
||||||
|
|
||||||
|
// lint is gofmt and go vet of one go.mod directory d of the tree, run with run in the directory's go environment, over
|
||||||
|
// the packages the level runs there (patterns, as packagesByDir gives them; issue 1921, try speed S3); a failure of either
|
||||||
|
// is a Failure of the level naming what failed. go vet runs once with every tag the directory names, which vets the
|
||||||
|
// untagged files too; only a directory where a tag leaves a file out vets also without tags, both passes at once.
|
||||||
|
func (m *Module) lint(ctx context.Context, run forgekey.Runner, tree, d string, env, patterns []string) ([]Failure, error) {
|
||||||
|
targets, only := lintScope(tree, d, patterns)
|
||||||
|
files, tags, negated, err := unformatted(tree, d, only)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("reading the Go files of %s: %w", d, err)
|
return nil, fmt.Errorf("reading the Go files of %s: %w", d, err)
|
||||||
}
|
}
|
||||||
var failed []Failure
|
var failed []Failure
|
||||||
if len(files) > 0 {
|
if len(files) > 0 {
|
||||||
failed = append(failed, Failure{Package: d, Test: "gofmt",
|
failed = append(failed, Failure{Package: d, Test: "gofmt", Step: StepLint,
|
||||||
Output: "not as gofmt writes them (run gofmt -w): " + strings.Join(files, ", ")})
|
Output: "not as gofmt writes them (run gofmt -w): " + strings.Join(files, ", ")})
|
||||||
}
|
}
|
||||||
sets := [][]string{nil}
|
sets := [][]string{tags}
|
||||||
if len(tags) > 0 {
|
if len(tags) > 0 && negated {
|
||||||
sets = append(sets, tags)
|
sets = [][]string{nil, tags}
|
||||||
}
|
}
|
||||||
for _, set := range sets {
|
type vetRun struct {
|
||||||
|
name string
|
||||||
|
r forgekey.Ran
|
||||||
|
}
|
||||||
|
runs := make([]vetRun, len(sets))
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
for i, set := range sets {
|
||||||
args := []string{"-C", filepath.Join(tree, d), "vet"}
|
args := []string{"-C", filepath.Join(tree, d), "vet"}
|
||||||
name := "go vet"
|
name := "go vet"
|
||||||
if len(set) > 0 {
|
if len(set) > 0 {
|
||||||
args = append(args, "-tags", strings.Join(set, ","))
|
args = append(args, "-tags", strings.Join(set, ","))
|
||||||
name += " -tags " + strings.Join(set, ",")
|
name += " -tags " + strings.Join(set, ",")
|
||||||
}
|
}
|
||||||
args = append(args, "./...")
|
args = append(args, targets...)
|
||||||
r := run(ctx, env, "go", args...)
|
runs[i].name = name
|
||||||
if r.Err != "" {
|
wg.Add(1)
|
||||||
return nil, fmt.Errorf("%s in %s could not be run: %s", name, d, firstLine(r.Err+"\n"+r.Stderr))
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
runs[i].r = run(ctx, env, "go", args...)
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
wg.Wait()
|
||||||
|
for _, v := range runs {
|
||||||
|
if v.r.Err != "" {
|
||||||
|
return nil, fmt.Errorf("%s in %s could not be run: %s", v.name, d, firstLine(v.r.Err+"\n"+v.r.Stderr))
|
||||||
}
|
}
|
||||||
if r.Status != 0 {
|
if v.r.Status != 0 {
|
||||||
failed = append(failed, Failure{Package: d, Test: name, Output: strings.TrimSpace(r.Stderr + "\n" + r.Stdout)})
|
failed = append(failed, Failure{Package: d, Test: v.name, Step: StepLint, Output: strings.TrimSpace(v.r.Stderr + "\n" + v.r.Stdout)})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return failed, nil
|
return failed, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// lintAll lints each directory, up to m.parallel() at once like the level's tests; the failures join in directory order
|
// lintAll lints each directory, up to m.parallel() at once like the level's tests; the failures join in directory order
|
||||||
// and the error is the first directory's, so the answer does not depend on which ended first (issue 1930).
|
// and the error is the first directory's, so the answer does not depend on which ended first (issue 1930). byDir names
|
||||||
func (m *Module) lintAll(ctx context.Context, run forgekey.Runner, tree string, dirs []string, env func(d string) []string) ([]Failure, error) {
|
// the packages the level runs in each directory (packagesByDir); nil lints each directory whole. acquire takes a slot of
|
||||||
|
// the bound the lint shares with the ask's tests; nil gives it one of its own.
|
||||||
|
func (m *Module) lintAll(ctx context.Context, run forgekey.Runner, tree string, dirs []string, env func(d string) []string, byDir map[string][]string, acquire func(context.Context) (func(), error)) ([]Failure, error) {
|
||||||
type answer struct {
|
type answer struct {
|
||||||
failed []Failure
|
failed []Failure
|
||||||
err error
|
err error
|
||||||
}
|
}
|
||||||
answers := make([]answer, len(dirs))
|
answers := make([]answer, len(dirs))
|
||||||
slots := make(chan struct{}, m.parallel())
|
if acquire == nil {
|
||||||
|
acquire = boundOf(m.parallel())
|
||||||
|
}
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
var crash any // a panic of a lint goes on in the caller, as a panic of a test run does
|
var crash any // a panic of a lint goes on in the caller, as a panic of a test run does
|
||||||
var crashMu sync.Mutex
|
var crashMu sync.Mutex
|
||||||
for i, d := range dirs {
|
for i, d := range dirs {
|
||||||
|
release, err := acquire(ctx)
|
||||||
|
if err != nil {
|
||||||
|
answers[i].err = err
|
||||||
|
break
|
||||||
|
}
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
slots <- struct{}{}
|
|
||||||
go func() {
|
go func() {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
defer func() { <-slots }()
|
defer release()
|
||||||
defer func() {
|
defer func() {
|
||||||
if p := recover(); p != nil {
|
if p := recover(); p != nil {
|
||||||
crashMu.Lock()
|
crashMu.Lock()
|
||||||
@@ -154,7 +228,7 @@ func (m *Module) lintAll(ctx context.Context, run forgekey.Runner, tree string,
|
|||||||
crashMu.Unlock()
|
crashMu.Unlock()
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
answers[i].failed, answers[i].err = m.lint(ctx, run, tree, d, env(d))
|
answers[i].failed, answers[i].err = m.lint(ctx, run, tree, d, env(d), byDir[d])
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
|
|||||||
@@ -4,11 +4,13 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||||
"git.novox.be/novox/mesh-sdk/go/forgekey"
|
"git.novox.be/novox/mesh-sdk/go/forgekey"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -28,8 +30,9 @@ func writeTree(t *testing.T, files map[string]string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 1887/D7-go, ADR 0529 §1: an unformatted file fails the unit level naming the file (testdata and vendor are left
|
// 1887/D7-go, ADR 0529 §1: an unformatted file fails the unit level naming the file (testdata and vendor are left
|
||||||
// alone), and go vet runs with no tag and with every tag the files name together, integration and isolation included, so
|
// alone), and go vet runs with every tag the files name together, integration and isolation included, so a vet error
|
||||||
// a vet error behind isolation fails the level naming the run.
|
// behind isolation fails the level naming the run. One pass with every tag vets the untagged files too (issue 1921, try
|
||||||
|
// speed S3), as no file here is left out by a tag.
|
||||||
func TestTheUnitLevelRunsGofmtAndVetWithEveryTag(t *testing.T) {
|
func TestTheUnitLevelRunsGofmtAndVetWithEveryTag(t *testing.T) {
|
||||||
tree := writeTree(t, map[string]string{
|
tree := writeTree(t, map[string]string{
|
||||||
"go.mod": "module x\n\ngo 1.26\n",
|
"go.mod": "module x\n\ngo 1.26\n",
|
||||||
@@ -51,11 +54,11 @@ func TestTheUnitLevelRunsGofmtAndVetWithEveryTag(t *testing.T) {
|
|||||||
}
|
}
|
||||||
return forgekey.Ran{}
|
return forgekey.Ran{}
|
||||||
}
|
}
|
||||||
failed, err := (&Module{}).lint(context.Background(), run, tree, ".", nil)
|
failed, err := (&Module{}).lint(context.Background(), run, tree, ".", nil, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
if strings.Join(vetted, " | ") != "vet ./... | vet -tags integration,isolation ./..." {
|
if strings.Join(vetted, " | ") != "vet -tags integration,isolation ./..." {
|
||||||
t.Errorf("vet ran %v", vetted)
|
t.Errorf("vet ran %v", vetted)
|
||||||
}
|
}
|
||||||
if len(failed) != 2 || failed[0].Test != "gofmt" || !strings.Contains(failed[0].Output, "b.go") ||
|
if len(failed) != 2 || failed[0].Test != "gofmt" || !strings.Contains(failed[0].Output, "b.go") ||
|
||||||
@@ -67,14 +70,14 @@ func TestTheUnitLevelRunsGofmtAndVetWithEveryTag(t *testing.T) {
|
|||||||
}
|
}
|
||||||
// A formatted directory that vets passes.
|
// A formatted directory that vets passes.
|
||||||
clean := writeTree(t, map[string]string{"go.mod": "module y\n", "a.go": "package y\n"})
|
clean := writeTree(t, map[string]string{"go.mod": "module y\n", "a.go": "package y\n"})
|
||||||
if failed, err := (&Module{}).lint(context.Background(), func(context.Context, []string, string, ...string) forgekey.Ran { return forgekey.Ran{} }, clean, ".", nil); err != nil || len(failed) != 0 {
|
if failed, err := (&Module{}).lint(context.Background(), func(context.Context, []string, string, ...string) forgekey.Ran { return forgekey.Ran{} }, clean, ".", nil, nil); err != nil || len(failed) != 0 {
|
||||||
t.Errorf("a clean directory: %+v, %v", failed, err)
|
t.Errorf("a clean directory: %+v, %v", failed, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 1887/D7-go: through the step, the unit level of a tree holding an unformatted file fails, naming the file.
|
// 1887/D7-go: through the step, the unit level of a tree holding an unformatted file fails, naming the file.
|
||||||
func TestAnUnformattedFileFailsTheUnitLevel(t *testing.T) {
|
func TestAnUnformattedFileFailsTheUnitLevel(t *testing.T) {
|
||||||
m, states, ws, calls := workspaceFor(t, "empty")
|
m, states, ws, _ := workspaceFor(t, "empty")
|
||||||
tree := filepath.Join(m.Work, ws, "tree")
|
tree := filepath.Join(m.Work, ws, "tree")
|
||||||
for name, body := range map[string]string{"go.mod": "module x\n", "x.go": "package x\nfunc X() {}\n"} {
|
for name, body := range map[string]string{"go.mod": "module x\n", "x.go": "package x\nfunc X() {}\n"} {
|
||||||
if err := os.WriteFile(filepath.Join(tree, name), []byte(body), 0o644); err != nil {
|
if err := os.WriteFile(filepath.Join(tree, name), []byte(body), 0o644); err != nil {
|
||||||
@@ -85,50 +88,117 @@ func TestAnUnformattedFileFailsTheUnitLevel(t *testing.T) {
|
|||||||
if res.Passed || len(res.Failed) != 1 || res.Failed[0].Test != "gofmt" || !strings.Contains(res.Failed[0].Output, "x.go") {
|
if res.Passed || len(res.Failed) != 1 || res.Failed[0].Test != "gofmt" || !strings.Contains(res.Failed[0].Output, "x.go") {
|
||||||
t.Fatalf("result = %+v, want the level failed naming x.go", res)
|
t.Fatalf("result = %+v, want the level failed naming x.go", res)
|
||||||
}
|
}
|
||||||
// Review round 2 item 5: before the level's tests, which do not run once gofmt failed.
|
}
|
||||||
for _, c := range *calls {
|
|
||||||
if strings.Contains(c.line, " test -json") {
|
// 1921/S4: the lint runs beside the unit level's tests, not before them: both have started before either ends, a lint
|
||||||
t.Errorf("the tests ran after gofmt failed: %s", c.line)
|
// failure still fails the level, and the result reports the lint's time and the tests' failures both.
|
||||||
|
func TestTheLintRunsBesideTheUnitTestsAndItsFailureStillFailsTheLevel(t *testing.T) {
|
||||||
|
m, states, ws, _ := workspaceFor(t, "failing")
|
||||||
|
tree := filepath.Join(m.Work, ws, "tree")
|
||||||
|
for name, body := range map[string]string{"go.mod": "module fixture/failing\n", "x.go": "package x\nfunc X() {}\n"} {
|
||||||
|
if err := os.WriteFile(filepath.Join(tree, name), []byte(body), 0o644); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
m.Parallel = 4
|
||||||
|
inner := m.Run
|
||||||
|
var vetStarted, testStarted sync.Once
|
||||||
|
vet, test := make(chan struct{}), make(chan struct{})
|
||||||
|
timeout := time.After(2 * time.Second)
|
||||||
|
m.Run = func(ctx context.Context, env []string, name string, args ...string) forgekey.Ran {
|
||||||
|
line := strings.Join(args, " ")
|
||||||
|
switch {
|
||||||
|
case strings.Contains(line, " vet "):
|
||||||
|
vetStarted.Do(func() { close(vet) })
|
||||||
|
select { // the vet ends only once a test has started
|
||||||
|
case <-test:
|
||||||
|
case <-timeout:
|
||||||
|
t.Error("no test started while the vet ran")
|
||||||
|
}
|
||||||
|
case strings.Contains(line, " test -json"):
|
||||||
|
testStarted.Do(func() { close(test) })
|
||||||
|
select {
|
||||||
|
case <-vet:
|
||||||
|
case <-timeout:
|
||||||
|
t.Error("no vet started while the tests ran")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return inner(ctx, env, name, args...)
|
||||||
|
}
|
||||||
|
res, st := runUnit(t, m, states, ws)
|
||||||
|
if st.State != delivery.StepPassed {
|
||||||
|
t.Fatalf("the step ended %s: %s", st.State, st.Reason)
|
||||||
|
}
|
||||||
|
if res.Passed || len(res.Failed) < 2 || res.Failed[0].Test != "gofmt" || res.Failed[1].Test != "TestThreeIsFour" {
|
||||||
|
t.Fatalf("failed = %+v, want gofmt's failure, then the test's", res.Failed)
|
||||||
|
}
|
||||||
|
// ADR 0542 §2: the lint's failure is the lint step's, not the unit level's.
|
||||||
|
if res.Failed[0].Step != StepLint || res.Failed[1].Step != "" {
|
||||||
|
t.Errorf("steps = %q, %q; want the gofmt failure the lint step's and the test's the level's", res.Failed[0].Step, res.Failed[1].Step)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Issue 1930: the lint of many directories runs side by side up to the level's bound, not one after another, and the
|
// 1921/S3: the lint of a level that runs some packages vets those packages alone, in one pass with every tag their
|
||||||
// failures still join in directory order.
|
// directory names, and reports a gofmt fault only in their files: a fault in a package the level keeps was reported
|
||||||
func TestTheLintOfManyDirectoriesRunsAtOnceUpToTheBound(t *testing.T) {
|
// when that package changed.
|
||||||
files := map[string]string{}
|
func TestTheLintOfSomePackagesVetsThemInOnePassAndFormatsOnlyTheirFiles(t *testing.T) {
|
||||||
var dirs []string
|
tree := writeTree(t, map[string]string{
|
||||||
for _, d := range []string{"a", "b", "c", "d", "e", "f"} {
|
"go.mod": "module x\n\ngo 1.26\n",
|
||||||
files[d+"/go.mod"] = "module x/" + d + "\n"
|
"a/a.go": "package a\n\nfunc A() {}\n",
|
||||||
files[d+"/x.go"] = "package x\n"
|
"a/a_integration_test.go": "//go:build integration\n\npackage a\n",
|
||||||
dirs = append(dirs, d)
|
"b/b.go": "package b\nfunc B() {}\n",
|
||||||
}
|
"a/deeper/d.go": "package deeper\nfunc D() {}\n",
|
||||||
tree := writeTree(t, files)
|
})
|
||||||
var mu sync.Mutex
|
var vetted []string
|
||||||
running, most := 0, 0
|
|
||||||
run := func(ctx context.Context, env []string, name string, args ...string) forgekey.Ran {
|
run := func(ctx context.Context, env []string, name string, args ...string) forgekey.Ran {
|
||||||
mu.Lock()
|
vetted = append(vetted, strings.Join(args[2:], " "))
|
||||||
running++
|
|
||||||
most = max(most, running)
|
|
||||||
mu.Unlock()
|
|
||||||
time.Sleep(30 * time.Millisecond)
|
|
||||||
mu.Lock()
|
|
||||||
running--
|
|
||||||
mu.Unlock()
|
|
||||||
if strings.HasSuffix(args[1], "b") || strings.HasSuffix(args[1], "e") {
|
|
||||||
return forgekey.Ran{Status: 1, Stderr: "bad " + filepath.Base(args[1])}
|
|
||||||
}
|
|
||||||
return forgekey.Ran{}
|
return forgekey.Ran{}
|
||||||
}
|
}
|
||||||
m := &Module{Parallel: 3}
|
failed, err := (&Module{}).lint(context.Background(), run, tree, ".", nil, []string{"x/a"})
|
||||||
failed, err := m.lintAll(context.Background(), run, tree, dirs, func(string) []string { return nil })
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
if most < 2 || most > 3 {
|
if strings.Join(vetted, " | ") != "vet -tags integration x/a" {
|
||||||
t.Errorf("%d lint commands ran at once, want 2 or 3 (the bound)", most)
|
t.Errorf("vet ran %v, want one pass over x/a with its tag", vetted)
|
||||||
}
|
}
|
||||||
if len(failed) != 2 || failed[0].Package != "b" || failed[1].Package != "e" {
|
if len(failed) != 0 {
|
||||||
t.Errorf("failed = %+v, want b's then e's", failed)
|
t.Errorf("the faults of packages the level keeps were reported: %+v", failed)
|
||||||
|
}
|
||||||
|
vetted = nil
|
||||||
|
failed, err = (&Module{}).lint(context.Background(), run, tree, ".", nil, []string{"x/b"})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(failed) != 1 || failed[0].Test != "gofmt" || !strings.Contains(failed[0].Output, "b/b.go") || strings.Contains(failed[0].Output, "d.go") {
|
||||||
|
t.Errorf("failed = %+v, want gofmt naming b/b.go alone", failed)
|
||||||
|
}
|
||||||
|
if strings.Join(vetted, " | ") != "vet -tags integration x/b" {
|
||||||
|
t.Errorf("vet ran %v", vetted)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1921/S3: a file a tag leaves out (//go:build !integration) is vetted by no pass with the tag, so a directory holding
|
||||||
|
// one keeps the pass without tags beside the pass with them.
|
||||||
|
func TestADirectoryWithAFileATagLeavesOutKeepsThePassWithoutTags(t *testing.T) {
|
||||||
|
tree := writeTree(t, map[string]string{
|
||||||
|
"go.mod": "module x\n",
|
||||||
|
"a.go": "package x\n",
|
||||||
|
"fake.go": "//go:build !integration\n\npackage x\n",
|
||||||
|
"real_test.go": "//go:build integration\n\npackage x\n",
|
||||||
|
})
|
||||||
|
var mu sync.Mutex
|
||||||
|
var vetted []string
|
||||||
|
run := func(ctx context.Context, env []string, name string, args ...string) forgekey.Ran {
|
||||||
|
mu.Lock()
|
||||||
|
vetted = append(vetted, strings.Join(args[2:], " "))
|
||||||
|
mu.Unlock()
|
||||||
|
return forgekey.Ran{}
|
||||||
|
}
|
||||||
|
if _, err := (&Module{}).lint(context.Background(), run, tree, ".", nil, nil); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
slices.Sort(vetted)
|
||||||
|
if strings.Join(vetted, " | ") != "vet -tags integration ./... | vet ./..." {
|
||||||
|
t.Errorf("vet ran %v, want the pass without tags and the pass with them", vetted)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||||
|
"git.novox.be/novox/mesh-sdk/go/forgekey"
|
||||||
)
|
)
|
||||||
|
|
||||||
func fetchOnce(t *testing.T, m *Module, states *keptStates, args map[string]any) FetchResult {
|
func fetchOnce(t *testing.T, m *Module, states *keptStates, args map[string]any) FetchResult {
|
||||||
@@ -188,3 +189,61 @@ func TestTheWorkspaceIsSelfContainedAfterAFetchThroughTheMirror(t *testing.T) {
|
|||||||
t.Errorf("the workspace is not whole: %v %s", err, out)
|
t.Errorf("the workspace is not whole: %v %s", err, out)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 1921/S6: with a base, the mirror is brought to the trunk commit too, and the merge preview deepens the tree from the
|
||||||
|
// mirror: it asks the forge for no history the mirror holds.
|
||||||
|
func TestTheMirrorIsBroughtToTheBaseAndThePreviewFetchesFromIt(t *testing.T) {
|
||||||
|
f := &fakeStep{gomods: "go.mod\n", rule: previewRules(false)}
|
||||||
|
m, states := fetchModule(t, f)
|
||||||
|
mirror := filepath.Join(m.Cache, "mirror", "novox", "mesh-sdk.git")
|
||||||
|
f.files = map[string]string{"modules/m/module.json": "{}\n", "modules/m/go.sum": "sum\n", "go.mod": "module x\n", "go.sum": ""}
|
||||||
|
fetchOnce(t, m, states, map[string]any{"repository": "novox/mesh-sdk", "commit": commitSHA, "base": baseSHA})
|
||||||
|
lines := strings.Join(f.lines(), "\n")
|
||||||
|
if !strings.Contains(lines, "-C "+mirror+" fetch --no-tags origin "+baseSHA) {
|
||||||
|
t.Errorf("the mirror was not brought to the base:\n%s", lines)
|
||||||
|
}
|
||||||
|
if !strings.Contains(lines, "fetch --no-tags --unshallow "+mirror+" "+baseSHA) || strings.Contains(lines, "--unshallow origin") {
|
||||||
|
t.Errorf("the preview fetched the trunk's history from the forge:\n%s", lines)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1921/S6: the siblings are placed side by side: each sibling's fetch has started before any of them ends.
|
||||||
|
func TestTheSiblingsArePlacedAtOnce(t *testing.T) {
|
||||||
|
var mu sync.Mutex
|
||||||
|
started := map[string]bool{}
|
||||||
|
both := make(chan struct{})
|
||||||
|
f := &fakeStep{gomods: "go.mod\n"}
|
||||||
|
f.rule = func(line string) (forgekey.Ran, bool) {
|
||||||
|
if !strings.Contains(line, "/beside/") || !strings.Contains(line, " fetch --no-tags --depth 1 ") {
|
||||||
|
return forgekey.Ran{}, false
|
||||||
|
}
|
||||||
|
mu.Lock()
|
||||||
|
for _, name := range []string{"mesh-catalog", "mesh-host"} {
|
||||||
|
if strings.Contains(line, "/beside/"+name) {
|
||||||
|
started[name] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(started) == 2 {
|
||||||
|
select {
|
||||||
|
case <-both:
|
||||||
|
default:
|
||||||
|
close(both)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mu.Unlock()
|
||||||
|
select {
|
||||||
|
case <-both:
|
||||||
|
case <-time.After(2 * time.Second):
|
||||||
|
return forgekey.Ran{Status: 1, Stderr: "the other sibling's fetch never started"}, true
|
||||||
|
}
|
||||||
|
return forgekey.Ran{}, false
|
||||||
|
}
|
||||||
|
m, states := fetchModule(t, f)
|
||||||
|
res := fetchOnce(t, m, states, map[string]any{"repository": "novox/mesh-controller", "commit": commitSHA, "beside": []any{
|
||||||
|
map[string]any{"repository": "novox/mesh-host", "commit": commitSHA},
|
||||||
|
map[string]any{"repository": "novox/mesh-catalog", "commit": commitSHA},
|
||||||
|
}})
|
||||||
|
if len(res.Beside) != 2 || res.Beside[0].Dir != "mesh-catalog" || res.Beside[1].Dir != "mesh-host" {
|
||||||
|
t.Errorf("beside = %+v, want both, in name order", res.Beside)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,103 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
// A failure that is not one test's assertion carries its real cause (issue 1901, its third meeting: a try of
|
|
||||||
// mesh-controller kept only "PASS" for a package that stopped half-way). Each fixture is the real `go test -json` of a
|
|
||||||
// throwaway package (testdata/captured/README).
|
|
||||||
func TestAFailureThatIsNotAnAssertionCarriesItsRealCause(t *testing.T) { // 1901/AC4
|
|
||||||
cases := []struct {
|
|
||||||
fixture string
|
|
||||||
test string // the test the failure names; "" for none
|
|
||||||
cause []string // what the cause holds
|
|
||||||
}{
|
|
||||||
{"panicking", "TestPanics", []string{"panic: assignment to entry in nil map"}},
|
|
||||||
{"timeout", "TestWaitsForever", []string{"panic: test timed out after 2s", "TestWaitsForever"}},
|
|
||||||
{"deeptimeout", "TestHoldsTheSeat, TestWaitsForTheBus", []string{"panic: test timed out after 2s", "TestHoldsTheSeat", "TestWaitsForTheBus"}},
|
|
||||||
{"buildfail", "", []string{`cannot use "seat" (untyped string constant) as int value`}},
|
|
||||||
{"exiting", "TestExits", []string{"exited", "TestExits"}},
|
|
||||||
{"killed", "TestKilled", []string{"signal: killed"}},
|
|
||||||
{"passthenfail", "", []string{"panic: the bus closed after the tests: nats: connection closed"}},
|
|
||||||
{"mainexit", "", []string{"passed", "exited"}},
|
|
||||||
{"race", "TestRacesOnTheSeat", []string{"DATA RACE", "Write at", "example.com/race.TestRacesOnTheSeat.func1()", "p_test.go:13", "created at example.com/race.TestRacesOnTheSeat()"}},
|
|
||||||
{"racemain", "", []string{"DATA RACE", "Write at", "example.com/racemain.TestMain.func1()", "p_test.go:15", "created at example.com/racemain.TestMain()"}},
|
|
||||||
}
|
|
||||||
for _, c := range cases {
|
|
||||||
t.Run(c.fixture, func(t *testing.T) {
|
|
||||||
raw, err := os.ReadFile("testdata/captured/go-test-json-pkg-" + c.fixture + ".jsonl")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
res := &TestResult{}
|
|
||||||
rd := newReader(res, ".")
|
|
||||||
rd.read(string(raw))
|
|
||||||
rd.finish()
|
|
||||||
if len(res.Failed) != 1 {
|
|
||||||
t.Fatalf("failures = %+v, want one", res.Failed)
|
|
||||||
}
|
|
||||||
f := res.Failed[0]
|
|
||||||
if f.Test != c.test {
|
|
||||||
t.Errorf("1901/AC4: the failure names test %q, want %q", f.Test, c.test)
|
|
||||||
}
|
|
||||||
for _, want := range c.cause {
|
|
||||||
if !strings.Contains(f.Cause, want) {
|
|
||||||
t.Errorf("1901/AC4: cause = %q, want it to hold %q", f.Cause, want)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(f.Cause) > outputLineBytes+3 {
|
|
||||||
t.Errorf("1901/AC4: cause holds %d bytes, over the line bound %d", len(f.Cause), outputLineBytes)
|
|
||||||
}
|
|
||||||
// Never a "PASS" or "ok" line as a failure's words, and the first line of the output, all an older
|
|
||||||
// checker reads, is the cause.
|
|
||||||
for _, l := range strings.Split(f.Output, "\n") {
|
|
||||||
if l = strings.TrimSpace(l); l == "PASS" || strings.HasPrefix(l, "ok ") || strings.HasPrefix(l, "ok\t") {
|
|
||||||
t.Errorf("1901/AC4: the output keeps the line %q: %q", l, f.Output)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if first := strings.SplitN(f.Output, "\n", 2)[0]; strings.TrimSpace(first) != strings.TrimSpace(f.Cause) {
|
|
||||||
t.Errorf("1901/AC4: the output starts with %q, want the cause %q", first, f.Cause)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// A package that passes, and a test that fails on its own assertion, keep what they had: no cause is made up for them.
|
|
||||||
func TestAnAssertionKeepsItsOwnCause(t *testing.T) { // 1901/AC4
|
|
||||||
raw, err := os.ReadFile("testdata/captured/go-test-json-log-then-fatal.jsonl")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
res := &TestResult{}
|
|
||||||
rd := newReader(res, ".")
|
|
||||||
rd.read(string(raw))
|
|
||||||
rd.finish()
|
|
||||||
if len(res.Failed) != 1 || !strings.HasSuffix(res.Failed[0].Cause, "3 subscription(s) derive to a subject no emitter owns:") {
|
|
||||||
t.Errorf("1901/AC4: failures = %+v", res.Failed)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// go prints the runtime's lines ("panic:", "fatal error:", "signal:", "WARNING: DATA RACE") at column 0: a test's own
|
|
||||||
// log line that holds the same words is indented, and is not the cause (review round 1). The fixture is the deeptimeout
|
|
||||||
// capture with two log lines of the first running test added: a t.Logf and its indented continuation saying "panic:".
|
|
||||||
func TestALoggedPanicIsNotTheCause(t *testing.T) { // 1901/AC4
|
|
||||||
raw, err := os.ReadFile("testdata/captured/go-test-json-pkg-deeptimeout.jsonl")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
lines := strings.SplitAfter(string(raw), "\n")
|
|
||||||
added := `{"Action":"output","Package":"example.com/deeptimeout","Test":"TestHoldsTheSeat","Output":" p_test.go:9: the child said:\n"}` + "\n" +
|
|
||||||
`{"Action":"output","Package":"example.com/deeptimeout","Test":"TestHoldsTheSeat","Output":" panic: child retried and recovered\n"}` + "\n" +
|
|
||||||
`{"Action":"output","Package":"example.com/deeptimeout","Test":"TestHoldsTheSeat","Output":" WARNING: DATA RACE\n"}` + "\n"
|
|
||||||
out := strings.Join(lines[:10], "") + added + strings.Join(lines[10:], "")
|
|
||||||
res := &TestResult{}
|
|
||||||
rd := newReader(res, ".")
|
|
||||||
rd.read(out)
|
|
||||||
rd.finish()
|
|
||||||
if len(res.Failed) != 1 || !strings.HasPrefix(res.Failed[0].Cause, "panic: test timed out after 2s") {
|
|
||||||
t.Errorf("1901/AC4: failures = %+v, want the timeout's panic as the cause", res.Failed)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -13,6 +13,7 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"git.novox.be/novox/mesh-sdk/go/buildstep"
|
"git.novox.be/novox/mesh-sdk/go/buildstep"
|
||||||
)
|
)
|
||||||
@@ -34,8 +35,9 @@ func (m *Module) remote(repository string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// merge makes the merge preview of the head onto base and writes the answer into res.
|
// merge makes the merge preview of the head onto base and writes the answer into res.
|
||||||
func (m *Module) merge(ctx context.Context, res *FetchResult, tree, remote, base, commit string) error {
|
// opts are the fetch's options, with the mirror when it holds base.
|
||||||
mr, err := buildstep.MergeInto(ctx, m.Run, tree, remote, base, commit, m.FetchOptions...)
|
func (m *Module) merge(ctx context.Context, res *FetchResult, tree, remote, base, commit string, opts []buildstep.Option) error {
|
||||||
|
mr, err := buildstep.MergeInto(ctx, m.Run, tree, remote, base, commit, opts...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -89,23 +91,22 @@ func (m *Module) besideDir(workspace string) string {
|
|||||||
// placeSiblings fetches each sibling into beside/<name> of the workspace root, as a plain directory. A symlink anywhere
|
// placeSiblings fetches each sibling into beside/<name> of the workspace root, as a plain directory. A symlink anywhere
|
||||||
// in a placed tree fails the step naming it: the mesh creates none, and a test must not be led out of its sibling.
|
// in a placed tree fails the step naming it: the mesh creates none, and a test must not be led out of its sibling.
|
||||||
func (m *Module) placeSiblings(ctx context.Context, root string, siblings []siblingArg) ([]Sibling, error) {
|
func (m *Module) placeSiblings(ctx context.Context, root string, siblings []siblingArg) ([]Sibling, error) {
|
||||||
var placed []Sibling
|
// The siblings are placed side by side (issue 1921, try speed S6); the first failure in the order given is the step's.
|
||||||
for _, s := range siblings {
|
placed := make([]Sibling, len(siblings))
|
||||||
name := s.repository[strings.Index(s.repository, "/")+1:]
|
errs := make([]error, len(siblings))
|
||||||
dir := filepath.Join(root, "beside", name)
|
var wg sync.WaitGroup
|
||||||
if err := os.MkdirAll(dir, 0o700); err != nil {
|
for i, s := range siblings {
|
||||||
|
wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
placed[i], errs[i] = m.placeSibling(ctx, root, s)
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
wg.Wait()
|
||||||
|
for _, err := range errs {
|
||||||
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if err := buildstep.FetchCommit(ctx, m.Run, dir, m.remote(s.repository), s.commit, s.ref, m.fetchOptions(ctx, s.repository, s.commit, s.ref)...); err != nil {
|
|
||||||
return nil, fmt.Errorf("beside %s: %w", s.repository, err)
|
|
||||||
}
|
|
||||||
if err := m.dissociate(ctx, dir); err != nil {
|
|
||||||
return nil, fmt.Errorf("beside %s: %w", s.repository, err)
|
|
||||||
}
|
|
||||||
if link := firstSymlink(dir); link != "" {
|
|
||||||
return nil, fmt.Errorf("beside %s holds a symlink, %s", s.repository, link)
|
|
||||||
}
|
|
||||||
placed = append(placed, Sibling{Repository: s.repository, Commit: s.commit, Dir: name})
|
|
||||||
}
|
}
|
||||||
sort.Slice(placed, func(i, j int) bool { return placed[i].Dir < placed[j].Dir })
|
sort.Slice(placed, func(i, j int) bool { return placed[i].Dir < placed[j].Dir })
|
||||||
if len(placed) > 0 {
|
if len(placed) > 0 {
|
||||||
@@ -121,6 +122,25 @@ func (m *Module) placeSiblings(ctx context.Context, root string, siblings []sibl
|
|||||||
return placed, nil
|
return placed, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// placeSibling fetches one sibling into beside/<name> of the workspace root.
|
||||||
|
func (m *Module) placeSibling(ctx context.Context, root string, s siblingArg) (Sibling, error) {
|
||||||
|
name := s.repository[strings.Index(s.repository, "/")+1:]
|
||||||
|
dir := filepath.Join(root, "beside", name)
|
||||||
|
if err := os.MkdirAll(dir, 0o700); err != nil {
|
||||||
|
return Sibling{}, err
|
||||||
|
}
|
||||||
|
if err := buildstep.FetchCommit(ctx, m.Run, dir, m.remote(s.repository), s.commit, s.ref, m.fetchOptions(ctx, s.repository, s.commit, s.ref)...); err != nil {
|
||||||
|
return Sibling{}, fmt.Errorf("beside %s: %w", s.repository, err)
|
||||||
|
}
|
||||||
|
if err := m.dissociate(ctx, dir); err != nil {
|
||||||
|
return Sibling{}, fmt.Errorf("beside %s: %w", s.repository, err)
|
||||||
|
}
|
||||||
|
if link := firstSymlink(dir); link != "" {
|
||||||
|
return Sibling{}, fmt.Errorf("beside %s holds a symlink, %s", s.repository, link)
|
||||||
|
}
|
||||||
|
return Sibling{Repository: s.repository, Commit: s.commit, Dir: name}, nil
|
||||||
|
}
|
||||||
|
|
||||||
// besideRecord is the file in a workspace's root naming the siblings the fetch placed beside it.
|
// besideRecord is the file in a workspace's root naming the siblings the fetch placed beside it.
|
||||||
const besideRecord = "beside.json"
|
const besideRecord = "beside.json"
|
||||||
|
|
||||||
|
|||||||
@@ -85,7 +85,8 @@ func TestFetchWithABaseMergesAndAnswersThePreview(t *testing.T) {
|
|||||||
t.Errorf("trees = %+v", res.Trees)
|
t.Errorf("trees = %+v", res.Trees)
|
||||||
}
|
}
|
||||||
lines := strings.Join(f.lines(), "\n")
|
lines := strings.Join(f.lines(), "\n")
|
||||||
for _, want := range []string{"fetch --no-tags --unshallow origin " + baseSHA, "checkout -q --detach " + baseSHA,
|
mirror := filepath.Join(m.Cache, "mirror", "novox", "mesh-sdk.git")
|
||||||
|
for _, want := range []string{"fetch --no-tags --unshallow " + mirror + " " + baseSHA, "checkout -q --detach " + baseSHA,
|
||||||
"core.hooksPath=/dev/null", "merge --no-ff --no-edit --no-verify"} {
|
"core.hooksPath=/dev/null", "merge --no-ff --no-edit --no-verify"} {
|
||||||
if !strings.Contains(lines, want) {
|
if !strings.Contains(lines, want) {
|
||||||
t.Errorf("git was not asked %q:\n%s", want, lines)
|
t.Errorf("git was not asked %q:\n%s", want, lines)
|
||||||
@@ -290,8 +291,8 @@ func TestTheIntegrationLevelIsGivenTheBesideDirectoryOnlyWhenSiblingsWerePlaced(
|
|||||||
}
|
}
|
||||||
tested := 0
|
tested := 0
|
||||||
for _, c := range f.calls {
|
for _, c := range f.calls {
|
||||||
if !strings.HasPrefix(c.line, "go ") || !strings.Contains(c.line, "-tags integration") {
|
if !strings.HasPrefix(c.line, "go ") || !strings.Contains(c.line, "-tags integration") || strings.Contains(c.line, "-exec true") {
|
||||||
continue
|
continue // the setup's compile runs no test binary, so it is given nothing a test reads (issue 1921, S5)
|
||||||
}
|
}
|
||||||
tested++
|
tested++
|
||||||
got := ""
|
got := ""
|
||||||
|
|||||||
@@ -24,9 +24,7 @@ func threeModules(t *testing.T, f *integFake) (*Module, *keptStates) {
|
|||||||
if err := os.MkdirAll(dir, 0o755); err != nil {
|
if err := os.MkdirAll(dir, 0o755); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
if err := os.WriteFile(filepath.Join(dir, "go.mod"), []byte("module fixture/"+d+"\n\ngo 1.25.0\n"), 0o644); err != nil {
|
writeTaggedTest(t, dir, "fixture/"+d)
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return m, states
|
return m, states
|
||||||
}
|
}
|
||||||
@@ -72,7 +70,7 @@ func TestOnlyTheSelectedDirectoryCompilesAndRuns(t *testing.T) {
|
|||||||
if !strings.HasPrefix(l, "go -C "+filepath.Join(tree, "b")+" test ") {
|
if !strings.HasPrefix(l, "go -C "+filepath.Join(tree, "b")+" test ") {
|
||||||
t.Errorf("go test ran outside the selected directory: %s", l)
|
t.Errorf("go test ran outside the selected directory: %s", l)
|
||||||
}
|
}
|
||||||
if strings.Contains(l, "-run ^$") {
|
if strings.Contains(l, "-exec true") {
|
||||||
compiled++
|
compiled++
|
||||||
} else {
|
} else {
|
||||||
ran++
|
ran++
|
||||||
@@ -91,7 +89,7 @@ func TestWithoutASelectionEveryDirectoryCompilesAndRuns(t *testing.T) {
|
|||||||
compiled, ran := 0, 0
|
compiled, ran := 0, 0
|
||||||
for _, l := range strings.Split(f.lines(), "\n") {
|
for _, l := range strings.Split(f.lines(), "\n") {
|
||||||
if strings.HasPrefix(l, "go ") && strings.Contains(l, " test ") {
|
if strings.HasPrefix(l, "go ") && strings.Contains(l, " test ") {
|
||||||
if strings.Contains(l, "-run ^$") {
|
if strings.Contains(l, "-exec true") {
|
||||||
compiled++
|
compiled++
|
||||||
} else {
|
} else {
|
||||||
ran++
|
ran++
|
||||||
|
|||||||
@@ -0,0 +1,81 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
// The bound on the processes one ask starts (issue 1921, try speed S10; ADR 0541): the levels of an ask run side by side,
|
||||||
|
// the unit level in this process and the integration level in a shut-off process of its own, and they share one bound,
|
||||||
|
// the processors divided by the node's build capacity. The bound lives in the ask's workspace as lock files, one per
|
||||||
|
// slot, so both processes count against it.
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
"strconv"
|
||||||
|
"sync/atomic"
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
// slotsDir is the directory in a workspace's root holding its slots.
|
||||||
|
const slotsDir = "slots"
|
||||||
|
|
||||||
|
// askDirs are the directories an ask keeps in its workspace's root beside its tree, gone with the workspace: its cache
|
||||||
|
// layer and its bound on processes.
|
||||||
|
var askDirs = []string{cacheLayerDir, slotsDir}
|
||||||
|
|
||||||
|
// numCPU is the node's processor count.
|
||||||
|
func numCPU() int { return runtime.NumCPU() }
|
||||||
|
|
||||||
|
// slotPool is the bound of one workspace: n slots, each a lock file under dir.
|
||||||
|
type slotPool struct {
|
||||||
|
dir string
|
||||||
|
n int
|
||||||
|
next *atomic.Uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
// slotTurns spreads the waits of the commands that find every slot taken over the slots.
|
||||||
|
var slotTurns atomic.Uint32
|
||||||
|
|
||||||
|
// slots is the bound of the workspace's processes, m.parallel() slots.
|
||||||
|
func (m *Module) slots(workspace string) slotPool {
|
||||||
|
return slotPool{dir: filepath.Join(m.Work, workspace, slotsDir), n: m.parallel(), next: &slotTurns}
|
||||||
|
}
|
||||||
|
|
||||||
|
// acquire takes a slot of the pool, waiting for one when every slot is taken, and answers what frees it. A free slot is
|
||||||
|
// taken at once; when none is, the wait is on one slot, taken in turn, until its holder frees it.
|
||||||
|
func (p slotPool) acquire(ctx context.Context) (func(), error) {
|
||||||
|
if err := os.MkdirAll(p.dir, 0o700); err != nil {
|
||||||
|
return nil, fmt.Errorf("the workspace's bound on processes: %w", err)
|
||||||
|
}
|
||||||
|
take := func(i int, how int) (func(), bool, error) {
|
||||||
|
f, err := os.OpenFile(filepath.Join(p.dir, "slot-"+strconv.Itoa(i)), os.O_CREATE|os.O_RDWR, 0o600)
|
||||||
|
if err != nil {
|
||||||
|
return nil, false, err
|
||||||
|
}
|
||||||
|
if err := syscall.Flock(int(f.Fd()), how); err != nil {
|
||||||
|
f.Close()
|
||||||
|
if err == syscall.EWOULDBLOCK {
|
||||||
|
return nil, false, nil
|
||||||
|
}
|
||||||
|
return nil, false, err
|
||||||
|
}
|
||||||
|
return func() { syscall.Flock(int(f.Fd()), syscall.LOCK_UN); f.Close() }, true, nil
|
||||||
|
}
|
||||||
|
for i := 0; i < p.n; i++ {
|
||||||
|
release, ok, err := take(i, syscall.LOCK_EX|syscall.LOCK_NB)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("the workspace's bound on processes: %w", err)
|
||||||
|
}
|
||||||
|
if ok {
|
||||||
|
return release, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := ctx.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
release, _, err := take(int(p.next.Add(1))%p.n, syscall.LOCK_EX)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("the workspace's bound on processes: %w", err)
|
||||||
|
}
|
||||||
|
return release, nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"strings"
|
||||||
|
"sync/atomic"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||||
|
"git.novox.be/novox/mesh-sdk/go/forgekey"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 1921/S10 (ADR 0541): the levels of one ask, running side by side, share one bound on the processes they start: the
|
||||||
|
// bound is the workspace's, so a slot another level of the ask holds is one this level waits for.
|
||||||
|
func TestTheLevelsOfAnAskShareOneBoundOnProcesses(t *testing.T) {
|
||||||
|
m, states, ws, _ := workspaceFor(t, "failing")
|
||||||
|
m.Parallel = 1
|
||||||
|
held, err := m.slots(ws).acquire(context.Background()) // the other level holds the one slot
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
var ran atomic.Int32
|
||||||
|
inner := m.Run
|
||||||
|
m.Run = func(ctx context.Context, env []string, name string, args ...string) forgekey.Ran {
|
||||||
|
if strings.Contains(strings.Join(args, " "), " go ") || name == "go" {
|
||||||
|
ran.Add(1)
|
||||||
|
}
|
||||||
|
return inner(ctx, env, name, args...)
|
||||||
|
}
|
||||||
|
done := make(chan delivery.Step, 1)
|
||||||
|
go func() { _, st := runUnit(t, m, states, ws); done <- st }()
|
||||||
|
time.Sleep(150 * time.Millisecond)
|
||||||
|
if n := ran.Load(); n != 0 {
|
||||||
|
t.Errorf("%d go commands ran while the ask's one slot was held by its other level", n)
|
||||||
|
}
|
||||||
|
held()
|
||||||
|
select {
|
||||||
|
case st := <-done:
|
||||||
|
if st.State != delivery.StepPassed {
|
||||||
|
t.Errorf("the step ended %s: %s", st.State, st.Reason)
|
||||||
|
}
|
||||||
|
case <-time.After(3 * time.Second):
|
||||||
|
t.Fatal("the level never ran after the slot was freed")
|
||||||
|
}
|
||||||
|
if ran.Load() == 0 {
|
||||||
|
t.Error("no go command ran")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1921/S10 (ADR 0541): the bound is the processors divided by the node's build capacity, not each level half of them.
|
||||||
|
func TestTheBoundIsTheProcessorsDividedByTheBuildCapacity(t *testing.T) {
|
||||||
|
m := &Module{Capacity: func() int { return 3 }}
|
||||||
|
if got, want := m.parallel(), max(1, numCPU()/3); got != want {
|
||||||
|
t.Errorf("parallel = %d, want %d", got, want)
|
||||||
|
}
|
||||||
|
m = &Module{}
|
||||||
|
if got, want := m.parallel(), max(1, numCPU()/2); got != want {
|
||||||
|
t.Errorf("with no capacity known parallel = %d, want %d", got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,161 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
// The integration level runs the tests its tags add, not the unit tests again (issue 1921, try speed S7): `go test
|
||||||
|
// -tags integration` without -run would run every untagged test too, and the unit level ran those already. Which tests a
|
||||||
|
// tag adds is read from the source: a test function in a _test.go file whose //go:build line holds with the level's
|
||||||
|
// tags and does not hold without them.
|
||||||
|
|
||||||
|
import (
|
||||||
|
"go/ast"
|
||||||
|
"go/build/constraint"
|
||||||
|
"go/parser"
|
||||||
|
"go/token"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
"unicode"
|
||||||
|
"unicode/utf8"
|
||||||
|
)
|
||||||
|
|
||||||
|
// taggedRun is what the integration level runs in one go.mod directory: the packages holding a test the tags add, by
|
||||||
|
// import path, and the -run pattern naming those tests.
|
||||||
|
type taggedRun struct {
|
||||||
|
packages []string
|
||||||
|
pattern string
|
||||||
|
}
|
||||||
|
|
||||||
|
// taggedTests reads, for each go.mod directory, the tests that tags add. selected are the packages the level was asked
|
||||||
|
// to run (import paths, as build_go_digest keys them); none, or a relative pattern, is every package. A directory with no
|
||||||
|
// test the tags add is left out of the answer.
|
||||||
|
func taggedTests(tree string, dirs, selected, tags []string) (map[string]taggedRun, error) {
|
||||||
|
want := map[string]bool{}
|
||||||
|
for _, p := range selected {
|
||||||
|
if strings.HasPrefix(p, "./") || p == "." {
|
||||||
|
want = nil
|
||||||
|
break
|
||||||
|
}
|
||||||
|
want[p] = true
|
||||||
|
}
|
||||||
|
if len(selected) == 0 {
|
||||||
|
want = nil
|
||||||
|
}
|
||||||
|
with := map[string]bool{}
|
||||||
|
for _, t := range tags {
|
||||||
|
with[t] = true
|
||||||
|
}
|
||||||
|
out := map[string]taggedRun{}
|
||||||
|
for _, d := range dirs {
|
||||||
|
raw, _ := os.ReadFile(filepath.Join(tree, d, "go.mod"))
|
||||||
|
mp := modulePath(string(raw))
|
||||||
|
base := filepath.Join(tree, d)
|
||||||
|
pkgs := map[string]bool{}
|
||||||
|
names := map[string]bool{}
|
||||||
|
err := lintable(base, func(path string) error {
|
||||||
|
if !strings.HasSuffix(path, "_test.go") {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
src, err := os.ReadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
expr := buildLine(src)
|
||||||
|
if expr == nil || !expr.Eval(func(tag string) bool { return with[tag] || platformTag(tag) }) || expr.Eval(platformTag) {
|
||||||
|
return nil // no constraint, or one the tags do not change: the unit level's
|
||||||
|
}
|
||||||
|
rel, _ := filepath.Rel(base, filepath.Dir(path))
|
||||||
|
pkg := mp
|
||||||
|
if rel != "." {
|
||||||
|
pkg = mp + "/" + filepath.ToSlash(rel)
|
||||||
|
}
|
||||||
|
if want != nil && !want[pkg] {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
found := testNames(path, src)
|
||||||
|
for _, n := range found {
|
||||||
|
names[n] = true
|
||||||
|
}
|
||||||
|
if len(found) > 0 {
|
||||||
|
pkgs[pkg] = true
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(pkgs) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
run := taggedRun{packages: sortedKeys(pkgs)}
|
||||||
|
run.pattern = "^(" + strings.Join(sortedKeys(names), "|") + ")$"
|
||||||
|
out[d] = run
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func sortedKeys(m map[string]bool) []string {
|
||||||
|
out := make([]string, 0, len(m))
|
||||||
|
for k := range m {
|
||||||
|
out = append(out, k)
|
||||||
|
}
|
||||||
|
sort.Strings(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// buildLine is a file's //go:build constraint, nil when it has none.
|
||||||
|
func buildLine(src []byte) constraint.Expr {
|
||||||
|
for _, line := range strings.Split(string(src), "\n") {
|
||||||
|
line = strings.TrimSpace(line)
|
||||||
|
if strings.HasPrefix(line, "package ") {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if constraint.IsGoBuild(line) {
|
||||||
|
if e, err := constraint.Parse(line); err == nil {
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// platformTag is how a tag other than the level's evaluates, the same with the level's tags and without them: the
|
||||||
|
// machine's platform, unix, cgo and every Go version hold; any other tag does not.
|
||||||
|
func platformTag(tag string) bool {
|
||||||
|
switch tag {
|
||||||
|
case runtime.GOOS, runtime.GOARCH, "unix", "cgo", "gc":
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return strings.HasPrefix(tag, "go1.")
|
||||||
|
}
|
||||||
|
|
||||||
|
// testNames are the top-level test functions of a test file: func TestXxx, as go test runs them, TestMain left out.
|
||||||
|
func testNames(path string, src []byte) []string {
|
||||||
|
f, err := parser.ParseFile(token.NewFileSet(), path, src, parser.SkipObjectResolution)
|
||||||
|
if err != nil {
|
||||||
|
return nil // a file that does not parse fails the compile, which the run reports
|
||||||
|
}
|
||||||
|
var out []string
|
||||||
|
for _, decl := range f.Decls {
|
||||||
|
fn, ok := decl.(*ast.FuncDecl)
|
||||||
|
if !ok || fn.Recv != nil || fn.Name.Name == "TestMain" || !isTestName(fn.Name.Name) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
out = append(out, fn.Name.Name)
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// isTestName says whether go test runs a function of this name as a test: Test, then nothing or a rune that is not
|
||||||
|
// lower case.
|
||||||
|
func isTestName(name string) bool {
|
||||||
|
rest, ok := strings.CutPrefix(name, "Test")
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if rest == "" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
r, _ := utf8.DecodeRuneInString(rest)
|
||||||
|
return !unicode.IsLower(r)
|
||||||
|
}
|
||||||
@@ -10,7 +10,6 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"regexp"
|
"regexp"
|
||||||
"runtime"
|
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
@@ -171,6 +170,9 @@ type levelOpts struct {
|
|||||||
// parallelism, where the unit level's is the slowest package's.
|
// parallelism, where the unit level's is the slowest package's.
|
||||||
setup time.Duration
|
setup time.Duration
|
||||||
wall bool
|
wall bool
|
||||||
|
// runByDir is the -run pattern of each go.mod directory, over run: the tests the integration level's tags add there
|
||||||
|
// (issue 1921, try speed S7).
|
||||||
|
runByDir map[string]string
|
||||||
// compose is a state of the level to put in the result (the store and the bus it ran against).
|
// compose is a state of the level to put in the result (the store and the bus it ran against).
|
||||||
store, bus string
|
store, bus string
|
||||||
}
|
}
|
||||||
@@ -205,35 +207,56 @@ func (m *Module) runLevel(ctx context.Context, tree string, o levelOpts) (*TestR
|
|||||||
todo = append(todo, d)
|
todo = append(todo, d)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// The level's commands share one bound on processes with each other and with the ask's other level: the lint's and
|
||||||
|
// the tests' (issue 1921, try speed S4, S10).
|
||||||
|
slots := m.slots(o.workspace)
|
||||||
|
var lintFailed []Failure
|
||||||
|
var lintErr error
|
||||||
|
var lintDone chan struct{}
|
||||||
|
var crash any // a panic of a run or of the lint goes on in the caller, which cleans up after it
|
||||||
|
var crashMu sync.Mutex
|
||||||
if o.level == LevelUnit {
|
if o.level == LevelUnit {
|
||||||
// gofmt and go vet of each directory the level runs come before its tests (ADR 0529 §1; issue 1887 D7-go): a
|
// gofmt and go vet of each directory the level runs, beside its tests (ADR 0529 §1; issue 1887 D7-go; issue 1921,
|
||||||
// directory that fails them fails the level at once, and their time is the result's Lint.
|
// try speed S4): a directory that fails them fails the level, and their time is the result's Lint.
|
||||||
linted := m.clock()
|
lintDone = make(chan struct{})
|
||||||
failed, err := m.lintAll(ctx, run, tree, todo, func(d string) []string {
|
go func() {
|
||||||
return append(m.goEnvFor(filepath.Join(tree, d)), o.env...)
|
defer close(lintDone)
|
||||||
})
|
defer func() {
|
||||||
if err != nil {
|
if p := recover(); p != nil {
|
||||||
return nil, err
|
crashMu.Lock()
|
||||||
}
|
crash = p
|
||||||
res.Lint = m.clock().Sub(linted).Seconds()
|
crashMu.Unlock()
|
||||||
if len(failed) > 0 {
|
}
|
||||||
res.Failed = failed
|
}()
|
||||||
res.judge(o.bounds)
|
linted := m.clock()
|
||||||
return res, nil
|
// go vet compiles and runs nothing of the change, so it warms the shared cache for every later check, a pull
|
||||||
}
|
// request's head included (issue 1921, try speed S11; the operator's decision); the tests run beneath the
|
||||||
began = m.clock() // the level's running time is its tests'
|
// ask's own layer.
|
||||||
|
compile, err := m.stepRunner(o.workspace, true)
|
||||||
|
if err != nil {
|
||||||
|
lintErr = err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
lintFailed, lintErr = m.lintAll(ctx, compile, tree, todo, func(d string) []string {
|
||||||
|
return append(m.goEnvFor(filepath.Join(tree, d)), o.env...)
|
||||||
|
}, byDir, slots.acquire)
|
||||||
|
lintFailed = append(m.addedBinaries(ctx, m.Run, tree), lintFailed...) // git alone, no code of the change
|
||||||
|
res.Lint = m.clock().Sub(linted).Seconds()
|
||||||
|
}()
|
||||||
}
|
}
|
||||||
runs := make([]dirRun, len(todo))
|
runs := make([]dirRun, len(todo))
|
||||||
slots := make(chan struct{}, m.parallel())
|
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
var crash any // a panic of a run goes on in the caller, which cleans up after it
|
var slotErr error
|
||||||
var crashMu sync.Mutex
|
|
||||||
for i, d := range todo {
|
for i, d := range todo {
|
||||||
|
release, err := slots.acquire(ctx)
|
||||||
|
if err != nil {
|
||||||
|
slotErr = err
|
||||||
|
break
|
||||||
|
}
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
slots <- struct{}{}
|
|
||||||
go func() {
|
go func() {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
defer func() { <-slots }()
|
defer release()
|
||||||
defer func() {
|
defer func() {
|
||||||
if p := recover(); p != nil {
|
if p := recover(); p != nil {
|
||||||
crashMu.Lock()
|
crashMu.Lock()
|
||||||
@@ -245,7 +268,9 @@ func (m *Module) runLevel(ctx context.Context, tree string, o levelOpts) (*TestR
|
|||||||
if len(o.tags) > 0 {
|
if len(o.tags) > 0 {
|
||||||
args = append(args, "-tags", strings.Join(o.tags, ","))
|
args = append(args, "-tags", strings.Join(o.tags, ","))
|
||||||
}
|
}
|
||||||
if o.run != "" {
|
if r := o.runByDir[d]; r != "" {
|
||||||
|
args = append(args, "-run", r)
|
||||||
|
} else if o.run != "" {
|
||||||
args = append(args, "-run", o.run)
|
args = append(args, "-run", o.run)
|
||||||
}
|
}
|
||||||
args = append(args, byDir[d]...)
|
args = append(args, byDir[d]...)
|
||||||
@@ -258,9 +283,19 @@ func (m *Module) runLevel(ctx context.Context, tree string, o levelOpts) (*TestR
|
|||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
|
if lintDone != nil {
|
||||||
|
<-lintDone
|
||||||
|
}
|
||||||
if crash != nil {
|
if crash != nil {
|
||||||
panic(crash)
|
panic(crash)
|
||||||
}
|
}
|
||||||
|
if lintErr != nil {
|
||||||
|
return nil, lintErr
|
||||||
|
}
|
||||||
|
if slotErr != nil {
|
||||||
|
return nil, slotErr
|
||||||
|
}
|
||||||
|
res.Failed = append(res.Failed, lintFailed...) // the lint's failures first, then the tests'
|
||||||
for i, d := range todo {
|
for i, d := range todo {
|
||||||
dr := runs[i]
|
dr := runs[i]
|
||||||
if (dr.r.Status != 0 || dr.r.Err != "") && len(dr.rd.pkgs) == 0 {
|
if (dr.r.Status != 0 || dr.r.Err != "") && len(dr.rd.pkgs) == 0 {
|
||||||
@@ -286,12 +321,20 @@ func (m *Module) runLevel(ctx context.Context, tree string, o levelOpts) (*TestR
|
|||||||
// runAnswer is what a step runner answers.
|
// runAnswer is what a step runner answers.
|
||||||
type runAnswer = forgekey.Ran
|
type runAnswer = forgekey.Ran
|
||||||
|
|
||||||
// parallel is how many directories a level runs at once.
|
// parallel is how many processes the levels of one ask start at once, together (ADR 0541; issue 1921, try speed S10):
|
||||||
|
// the processors divided by the node's build capacity, half of them when the capacity is not known.
|
||||||
func (m *Module) parallel() int {
|
func (m *Module) parallel() int {
|
||||||
if m.Parallel > 0 {
|
if m.Parallel > 0 {
|
||||||
return m.Parallel
|
return m.Parallel
|
||||||
}
|
}
|
||||||
return max(1, runtime.NumCPU()/2)
|
capacity := 0
|
||||||
|
if m.Capacity != nil {
|
||||||
|
capacity = m.Capacity()
|
||||||
|
}
|
||||||
|
if capacity <= 0 {
|
||||||
|
capacity = 2
|
||||||
|
}
|
||||||
|
return max(1, numCPU()/capacity)
|
||||||
}
|
}
|
||||||
|
|
||||||
// packagesByDir says which patterns run in which go.mod directory. A relative pattern (./...) runs in every directory, as
|
// packagesByDir says which patterns run in which go.mod directory. A relative pattern (./...) runs in every directory, as
|
||||||
|
|||||||
@@ -66,7 +66,9 @@ func TestUnitLevelRunsTheRealGoOnTheFixtureRepositories(t *testing.T) {
|
|||||||
"tagged": {passed: true, noTests: true},
|
"tagged": {passed: true, noTests: true},
|
||||||
"empty": {passed: true, noTests: true},
|
"empty": {passed: true, noTests: true},
|
||||||
"slow": {passed: true},
|
"slow": {passed: true},
|
||||||
"broken": {failed: "."}, // go vet, before the tests since R4g (ADR 0529 §1), names the directory and the compiler's line
|
// go vet, the lint step beside the tests (ADR 0542 §2), names the directory and the compiler's line; the test run
|
||||||
|
// of the package that does not compile fails beside it
|
||||||
|
"broken": {failed: "."},
|
||||||
}
|
}
|
||||||
for name, w := range want {
|
for name, w := range want {
|
||||||
home := t.TempDir()
|
home := t.TempDir()
|
||||||
@@ -84,14 +86,16 @@ func TestUnitLevelRunsTheRealGoOnTheFixtureRepositories(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("%s: %v", name, err)
|
t.Fatalf("%s: %v", name, err)
|
||||||
}
|
}
|
||||||
if res.Passed != w.passed || res.NoTests != w.noTests || (w.failed != "" && (len(res.Failed) != 1 || res.Failed[0].Package != w.failed)) {
|
lintFirst := w.failed != "." || (len(res.Failed) >= 1 && res.Failed[0].Step == StepLint)
|
||||||
|
if res.Passed != w.passed || res.NoTests != w.noTests || !lintFirst ||
|
||||||
|
(w.failed != "" && (len(res.Failed) == 0 || res.Failed[0].Package != w.failed)) {
|
||||||
t.Errorf("%s: result = %+v", name, res)
|
t.Errorf("%s: result = %+v", name, res)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 1887/D7-go against the real go: a tree holding an unformatted file fails the unit level naming the file, before any
|
// 1887/D7-go against the real go: a tree holding an unformatted file fails the check naming the file, as the lint step's
|
||||||
// test runs, and the level's result carries the lint's time.
|
// failure (ADR 0542 §2), and the level's result carries the lint's time.
|
||||||
func TestAnUnformattedFileFailsTheRealUnitLevel(t *testing.T) {
|
func TestAnUnformattedFileFailsTheRealUnitLevel(t *testing.T) {
|
||||||
needTools(t, "the unit level's gofmt and vet", "go", "git", "bwrap")
|
needTools(t, "the unit level's gofmt and vet", "go", "git", "bwrap")
|
||||||
home := t.TempDir()
|
home := t.TempDir()
|
||||||
@@ -111,8 +115,9 @@ func TestAnUnformattedFileFailsTheRealUnitLevel(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
if res.Passed || len(res.Failed) == 0 || res.Failed[0].Test != "gofmt" || !strings.Contains(res.Failed[0].Output, "ugly.go") || res.Tests != 0 {
|
if res.Passed || len(res.Failed) == 0 || res.Failed[0].Test != "gofmt" || res.Failed[0].Step != StepLint ||
|
||||||
t.Fatalf("result = %+v, want the level failed by gofmt naming ugly.go and no test run", res)
|
!strings.Contains(res.Failed[0].Output, "ugly.go") || res.Lint <= 0 {
|
||||||
|
t.Fatalf("result = %+v, want the lint step failed by gofmt naming ugly.go, with its time", res)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -156,9 +161,8 @@ func TestIntegrationLevelRunsAgainstARealStoreAndBusAndLeavesNothing(t *testing.
|
|||||||
if !res.Passed || res.Tests != 1 || res.Setup <= 0 || res.Setup > IntegrationSetup.Seconds() {
|
if !res.Passed || res.Tests != 1 || res.Setup <= 0 || res.Setup > IntegrationSetup.Seconds() {
|
||||||
t.Errorf("result = %+v", res)
|
t.Errorf("result = %+v", res)
|
||||||
}
|
}
|
||||||
entries, _ := os.ReadDir(filepath.Join(work, "ws1"))
|
if left := workspaceLeft(filepath.Join(work, "ws1")); len(left) != 1 || left[0] != "tree" {
|
||||||
if len(entries) != 1 || entries[0].Name() != "tree" {
|
t.Errorf("the workspace holds %v besides the ask's own directories, want only its tree: the run directory is gone", left)
|
||||||
t.Errorf("the workspace holds %v, want only its tree: the run directory is gone", entries)
|
|
||||||
}
|
}
|
||||||
for deadline := time.Now().Add(5 * time.Second); len(processesNamed(work)) > 0 && time.Now().Before(deadline); time.Sleep(100 * time.Millisecond) {
|
for deadline := time.Now().Add(5 * time.Second); len(processesNamed(work)) > 0 && time.Now().Before(deadline); time.Sleep(100 * time.Millisecond) {
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -25,8 +26,11 @@ func capturedRunner(t *testing.T, captured string, calls *[]cmdRecord) forgekey.
|
|||||||
if strings.Contains(string(raw), `"Action":"fail"`) {
|
if strings.Contains(string(raw), `"Action":"fail"`) {
|
||||||
status = 1 // go test exits 1 when a test or a build failed
|
status = 1 // go test exits 1 when a test or a build failed
|
||||||
}
|
}
|
||||||
|
var mu sync.Mutex // the lint and the tests of a level run at once (issue 1921, try speed S4)
|
||||||
return func(ctx context.Context, env []string, name string, args ...string) forgekey.Ran {
|
return func(ctx context.Context, env []string, name string, args ...string) forgekey.Ran {
|
||||||
|
mu.Lock()
|
||||||
*calls = append(*calls, cmdRecord{env: env, line: name + " " + strings.Join(args, " ")})
|
*calls = append(*calls, cmdRecord{env: env, line: name + " " + strings.Join(args, " ")})
|
||||||
|
mu.Unlock()
|
||||||
switch {
|
switch {
|
||||||
case name == "git" && args[2] == "ls-files":
|
case name == "git" && args[2] == "ls-files":
|
||||||
return forgekey.Ran{Stdout: "go.mod\n"}
|
return forgekey.Ran{Stdout: "go.mod\n"}
|
||||||
@@ -103,6 +107,33 @@ func TestUnitLevelNamesTheFailingTestAndFailsTheLevel(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Issue 1921: a fixture with a failing test in each of two packages: one level's run names both, each with its own
|
||||||
|
// cause, and counts every test, the passing one beside a failure too. go test runs every package to its end; nothing
|
||||||
|
// stops at the first failure.
|
||||||
|
func TestUnitLevelNamesEveryFailingTestOfEveryPackage(t *testing.T) {
|
||||||
|
m, states, ws, calls := workspaceFor(t, "twofailing")
|
||||||
|
res, st := runUnit(t, m, states, ws)
|
||||||
|
if st.State != delivery.StepPassed || res.Passed {
|
||||||
|
t.Fatalf("step %s (%s), result %+v: want the step done and the level failed", st.State, st.Reason, res)
|
||||||
|
}
|
||||||
|
var got []string
|
||||||
|
for _, f := range res.Failed {
|
||||||
|
got = append(got, f.Package+"."+f.Test+": "+f.Cause)
|
||||||
|
}
|
||||||
|
want := []string{"fixture/twofailing/one.TestOneIsTwo: One() = 1, want 2", "fixture/twofailing/two.TestTwoIsThree: Two() = 2, want 3"}
|
||||||
|
if strings.Join(got, "\n") != strings.Join(want, "\n") {
|
||||||
|
t.Errorf("failed\n%s\nwant\n%s", strings.Join(got, "\n"), strings.Join(want, "\n"))
|
||||||
|
}
|
||||||
|
if res.Tests != 3 {
|
||||||
|
t.Errorf("%d tests counted, want 3", res.Tests)
|
||||||
|
}
|
||||||
|
for _, c := range *calls {
|
||||||
|
if strings.Contains(c.line, " test ") && strings.Contains(c.line, "-failfast") {
|
||||||
|
t.Errorf("go test ran with -failfast: %s", c.line)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 1290/AC2: a fixture whose tests take more than the bound (lowered by the module's option, not by waiting five
|
// 1290/AC2: a fixture whose tests take more than the bound (lowered by the module's option, not by waiting five
|
||||||
// seconds): the level fails naming the bound and the slowest tests; under the real bound the same run passes.
|
// seconds): the level fails naming the bound and the slowest tests; under the real bound the same run passes.
|
||||||
func TestUnitLevelOverItsBoundFailsNamingTheBoundAndTheSlowestTests(t *testing.T) {
|
func TestUnitLevelOverItsBoundFailsNamingTheBoundAndTheSlowestTests(t *testing.T) {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
Each file is the standard output of `GOTOOLCHAIN=go1.27.1 GOFLAGS=-mod=mod go test -count=1 -json ./...` (go1.27.1)
|
Each file is the standard output of `GOTOOLCHAIN=go1.27.1 GOFLAGS=-mod=mod go test -count=1 -json ./...` (go1.27.1)
|
||||||
run in the fixture module of the same name under ../repos, captured on 2026-10-20 for issue 1290. The unit
|
run in the fixture module of the same name under ../repos, captured on 2026-10-20 for issue 1290. The unit
|
||||||
tests feed them to the parser through a fake runner, so no test here runs `go test`.
|
tests feed them to the parser through a fake runner, so no test here runs `go test`. twofailing.json was captured the
|
||||||
|
same way on 2026-10-24 for issue 1921: two packages with a failing test each, and a passing test beside one of them.
|
||||||
|
|
||||||
golist-external.json is `GOTOOLCHAIN=go1.27.1 GOFLAGS=-mod=mod go list -json=ImportPath,Standard,DepOnly,Module
|
golist-external.json is `GOTOOLCHAIN=go1.27.1 GOFLAGS=-mod=mod go list -json=ImportPath,Standard,DepOnly,Module
|
||||||
git.novox.be/novox/mesh-sdk/go/buildstep` run in this module (go1.27.1, 2026-10-20 for issue 1291): a package of a module
|
git.novox.be/novox/mesh-sdk/go/buildstep` run in this module (go1.27.1, 2026-10-20 for issue 1291): a package of a module
|
||||||
@@ -24,15 +25,3 @@ The tar streams (the archives gunzipped) of the three archives above, captured t
|
|||||||
gunzip and sha256) at origin/main 0205b8f7 on 2026-10-21: directory f8af1d94...d1b6, within 294e2bec...07fb, file
|
gunzip and sha256) at origin/main 0205b8f7 on 2026-10-21: directory f8af1d94...d1b6, within 294e2bec...07fb, file
|
||||||
055cffc2...428e. The digests above hold the gzip bytes of go1.27.1. The first try of issue 1589 on the merge check's build node answered
|
055cffc2...428e. The digests above hold the gzip bytes of go1.27.1. The first try of issue 1589 on the merge check's build node answered
|
||||||
other digests; the tests therefore compare the stream always and the digest only under go1.27.1.
|
other digests; the tests therefore compare the stream always and the digest only under go1.27.1.
|
||||||
|
|
||||||
go-test-json-pkg-*.jsonl are the standard output and error of `go test -json -count=1 ./...` (go1.27.3, the timeouts with
|
|
||||||
`-timeout 2s`), captured on 2026-10-25 for issue 1901 in throwaway one-file modules that each fail without a test's own
|
|
||||||
assertion: panicking (a nil map write in TestPanics), timeout (TestWaitsForever sleeps past the timeout), deeptimeout (two
|
|
||||||
parallel tests and sixty blocked goroutines past the timeout, so the goroutine dump pushes the panic line out of the last
|
|
||||||
4 KB), buildfail (a type error in a test file), exiting (os.Exit(3) in TestExits), killed (TestKilled sends itself SIGKILL),
|
|
||||||
passthenfail (TestMain panics in a goroutine after m.Run passed) and mainexit (TestMain exits 2 after m.Run passed: go test
|
|
||||||
prints "PASS" and then "FAIL <pkg>").
|
|
||||||
go-test-json-pkg-race.jsonl and go-test-json-pkg-racemain.jsonl are the same, with `go test -race -json -count=1 ./...`
|
|
||||||
(CGO_ENABLED=1, go1.27.3, 2026-10-25, issue 1901): race (TestRacesOnTheSeat writes a package variable from itself and a
|
|
||||||
goroutine) and racemain (TestMain does so after m.Run passed: go test prints "PASS", then the race report and "Found 1
|
|
||||||
data race(s)", then "FAIL <pkg>").
|
|
||||||
|
|||||||
-6
@@ -1,6 +0,0 @@
|
|||||||
{"ImportPath":"example.com/buildfail [example.com/buildfail.test]","Action":"build-output","Output":"# example.com/buildfail [example.com/buildfail.test]\n"}
|
|
||||||
{"ImportPath":"example.com/buildfail [example.com/buildfail.test]","Action":"build-output","Output":"./p_test.go:5:40: cannot use \"seat\" (untyped string constant) as int value in variable declaration\n"}
|
|
||||||
{"ImportPath":"example.com/buildfail [example.com/buildfail.test]","Action":"build-fail"}
|
|
||||||
{"Time":"2026-10-25T15:34:33.261137737+01:00","Action":"start","Package":"example.com/buildfail"}
|
|
||||||
{"Time":"2026-10-25T15:34:33.261255019+01:00","Action":"output","Package":"example.com/buildfail","Output":"FAIL\texample.com/buildfail [build failed]\n","OutputType":"frame"}
|
|
||||||
{"Time":"2026-10-25T15:34:33.261268099+01:00","Action":"fail","Package":"example.com/buildfail","Elapsed":0,"FailedBuild":"example.com/buildfail [example.com/buildfail.test]"}
|
|
||||||
Vendored
-417
@@ -1,417 +0,0 @@
|
|||||||
{"Time":"2026-10-25T15:35:18.991630993+01:00","Action":"start","Package":"example.com/deeptimeout"}
|
|
||||||
{"Time":"2026-10-25T15:35:18.993895188+01:00","Action":"run","Package":"example.com/deeptimeout","Test":"TestHoldsTheSeat"}
|
|
||||||
{"Time":"2026-10-25T15:35:18.993963909+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestHoldsTheSeat","Output":"=== RUN TestHoldsTheSeat\n","OutputType":"frame"}
|
|
||||||
{"Time":"2026-10-25T15:35:18.99399484+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestHoldsTheSeat","Output":"=== PAUSE TestHoldsTheSeat\n","OutputType":"frame"}
|
|
||||||
{"Time":"2026-10-25T15:35:18.9939982+01:00","Action":"pause","Package":"example.com/deeptimeout","Test":"TestHoldsTheSeat"}
|
|
||||||
{"Time":"2026-10-25T15:35:18.99400173+01:00","Action":"run","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus"}
|
|
||||||
{"Time":"2026-10-25T15:35:18.99400456+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"=== RUN TestWaitsForTheBus\n","OutputType":"frame"}
|
|
||||||
{"Time":"2026-10-25T15:35:18.99400838+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"=== PAUSE TestWaitsForTheBus\n","OutputType":"frame"}
|
|
||||||
{"Time":"2026-10-25T15:35:18.99401133+01:00","Action":"pause","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus"}
|
|
||||||
{"Time":"2026-10-25T15:35:18.99401441+01:00","Action":"cont","Package":"example.com/deeptimeout","Test":"TestHoldsTheSeat"}
|
|
||||||
{"Time":"2026-10-25T15:35:18.99401704+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestHoldsTheSeat","Output":"=== CONT TestHoldsTheSeat\n","OutputType":"frame"}
|
|
||||||
{"Time":"2026-10-25T15:35:18.994074141+01:00","Action":"cont","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus"}
|
|
||||||
{"Time":"2026-10-25T15:35:18.994102612+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"=== CONT TestWaitsForTheBus\n","OutputType":"frame"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.00935195+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"panic: test timed out after 2s\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.00938383+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\trunning tests:\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.009394391+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t\tTestHoldsTheSeat (2s)\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.009398021+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t\tTestWaitsForTheBus (2s)\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.009401801+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.009406481+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"goroutine 81 [running]:\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.009409911+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"testing.(*M).startAlarm.func1()\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.009413361+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/usr/lib/go/src/testing/testing.go:2961 +0x34a\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.009418341+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"created by time.goFunc\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.009421781+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/usr/lib/go/src/time/sleep.go:182 +0x2d\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.009424991+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.009428341+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"goroutine 1 [chan receive]:\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.009431721+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"testing.tRunner.func1()\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.009441811+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/usr/lib/go/src/testing/testing.go:2144 +0x425\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.009445511+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"testing.tRunner(0x282e9702008, 0x282e9646bc8)\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.009449641+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/usr/lib/go/src/testing/testing.go:2201 +0x123\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.009453471+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"testing.runTests({0x55a45d, 0x17}, {0x55a45d, 0x17}, 0x282e95940f0, {0x6f19f0, 0x2, 0x2}, {0xc2afa2ca3b3c0c8d, 0x7737f168, ...})\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.009462092+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/usr/lib/go/src/testing/testing.go:2742 +0x510\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.009467222+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"testing.(*M).Run(0x282e96581e0)\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.009470662+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/usr/lib/go/src/testing/testing.go:2602 +0x6af\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.009473692+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"main.main()\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.009476942+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t_testmain.go:48 +0x9b\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.009480092+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.009483242+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"goroutine 7 [sleep]:\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.009486502+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"time.Sleep(0xdf8475800)\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.009490682+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/usr/lib/go/src/runtime/time.go:368 +0x165\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.009493812+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"example.com/deeptimeout.TestHoldsTheSeat(0x282e9702248?)\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.009498842+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:14 +0x9e\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.009502242+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"testing.tRunner(0x282e9702248, 0x6d5e78)\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.009505742+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/usr/lib/go/src/testing/testing.go:2195 +0xea\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.009509292+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"created by testing.(*T).Run in goroutine 1\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.009512752+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/usr/lib/go/src/testing/testing.go:2260 +0x4d4\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.009515792+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.009518623+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"goroutine 8 [sleep]:\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.009521303+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"time.Sleep(0xdf8475800)\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.009524423+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/usr/lib/go/src/runtime/time.go:368 +0x165\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.009531403+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"example.com/deeptimeout.TestWaitsForTheBus(0x282e9702488?)\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.009534943+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:19 +0x25\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.009538403+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"testing.tRunner(0x282e9702488, 0x6d5e80)\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.009541953+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/usr/lib/go/src/testing/testing.go:2195 +0xea\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.009545233+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"created by testing.(*T).Run in goroutine 1\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.009548323+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/usr/lib/go/src/testing/testing.go:2260 +0x4d4\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.009551343+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.009554263+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"goroutine 9 [chan receive]:\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.009557523+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"example.com/deeptimeout.TestHoldsTheSeat.func1()\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.009561063+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x19\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.009564513+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"created by example.com/deeptimeout.TestHoldsTheSeat in goroutine 7\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.009571113+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x37\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.009574233+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.009577433+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"goroutine 10 [chan receive]:\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.009580823+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"example.com/deeptimeout.TestHoldsTheSeat.func1()\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.009584174+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x19\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.009587504+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"created by example.com/deeptimeout.TestHoldsTheSeat in goroutine 7\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.009590974+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x37\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.009594004+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.009597184+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"goroutine 11 [chan receive]:\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.009600394+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"example.com/deeptimeout.TestHoldsTheSeat.func1()\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.009603874+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x19\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.009610954+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"created by example.com/deeptimeout.TestHoldsTheSeat in goroutine 7\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.009614714+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x37\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.009618104+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.009621364+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"goroutine 12 [chan receive]:\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.009624704+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"example.com/deeptimeout.TestHoldsTheSeat.func1()\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.009628324+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x19\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.009631954+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"created by example.com/deeptimeout.TestHoldsTheSeat in goroutine 7\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.009635404+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x37\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.009638734+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.009641904+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"goroutine 13 [chan receive]:\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.009645325+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"example.com/deeptimeout.TestHoldsTheSeat.func1()\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.009648645+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x19\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.009652065+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"created by example.com/deeptimeout.TestHoldsTheSeat in goroutine 7\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.009655625+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x37\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.009658885+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.009663385+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"goroutine 14 [chan receive]:\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.009666735+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"example.com/deeptimeout.TestHoldsTheSeat.func1()\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.009670235+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x19\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.009676385+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"created by example.com/deeptimeout.TestHoldsTheSeat in goroutine 7\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.009680205+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x37\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.009683315+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.009686475+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"goroutine 15 [chan receive]:\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.009689785+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"example.com/deeptimeout.TestHoldsTheSeat.func1()\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.009697235+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x19\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.009700795+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"created by example.com/deeptimeout.TestHoldsTheSeat in goroutine 7\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.009704985+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x37\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.009708085+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.009711346+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"goroutine 16 [chan receive]:\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.009714586+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"example.com/deeptimeout.TestHoldsTheSeat.func1()\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.009717946+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x19\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.009721676+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"created by example.com/deeptimeout.TestHoldsTheSeat in goroutine 7\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.009725626+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x37\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.009728866+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.009732106+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"goroutine 17 [chan receive]:\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.009735376+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"example.com/deeptimeout.TestHoldsTheSeat.func1()\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.009738956+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x19\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.009742326+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"created by example.com/deeptimeout.TestHoldsTheSeat in goroutine 7\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.009746436+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x37\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.009750666+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.009754126+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"goroutine 18 [chan receive]:\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.009758286+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"example.com/deeptimeout.TestHoldsTheSeat.func1()\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.009761846+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x19\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.009765776+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"created by example.com/deeptimeout.TestHoldsTheSeat in goroutine 7\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.009769816+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x37\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.009773167+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.009780507+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"goroutine 19 [chan receive]:\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.009784207+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"example.com/deeptimeout.TestHoldsTheSeat.func1()\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.009788007+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x19\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.009791627+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"created by example.com/deeptimeout.TestHoldsTheSeat in goroutine 7\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.009796347+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x37\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.009799427+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.009802937+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"goroutine 20 [chan receive]:\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.009806667+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"example.com/deeptimeout.TestHoldsTheSeat.func1()\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.009809987+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x19\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.009813827+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"created by example.com/deeptimeout.TestHoldsTheSeat in goroutine 7\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.009818087+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x37\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.009821397+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.009824227+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"goroutine 21 [chan receive]:\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.009827607+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"example.com/deeptimeout.TestHoldsTheSeat.func1()\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.009831307+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x19\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.009835167+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"created by example.com/deeptimeout.TestHoldsTheSeat in goroutine 7\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.009839528+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x37\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.009842728+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.009845888+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"goroutine 22 [chan receive]:\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.009849168+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"example.com/deeptimeout.TestHoldsTheSeat.func1()\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.009852548+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x19\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.009855958+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"created by example.com/deeptimeout.TestHoldsTheSeat in goroutine 7\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.009863648+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x37\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.009866938+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.009870088+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"goroutine 23 [chan receive]:\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.009879828+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"example.com/deeptimeout.TestHoldsTheSeat.func1()\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.009883728+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x19\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.009887608+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"created by example.com/deeptimeout.TestHoldsTheSeat in goroutine 7\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.009891548+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x37\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.009895238+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.009898748+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"goroutine 24 [chan receive]:\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.009902349+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"example.com/deeptimeout.TestHoldsTheSeat.func1()\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.009907109+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x19\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.009911519+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"created by example.com/deeptimeout.TestHoldsTheSeat in goroutine 7\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.009915609+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x37\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.009918939+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.009922329+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"goroutine 25 [chan receive]:\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.009928329+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"example.com/deeptimeout.TestHoldsTheSeat.func1()\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.009931909+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x19\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.009935489+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"created by example.com/deeptimeout.TestHoldsTheSeat in goroutine 7\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.009939429+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x37\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.009942699+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.009946019+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"goroutine 26 [chan receive]:\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.009949559+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"example.com/deeptimeout.TestHoldsTheSeat.func1()\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.009953489+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x19\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.00996244+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"created by example.com/deeptimeout.TestHoldsTheSeat in goroutine 7\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.00996647+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x37\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.00996987+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.00997314+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"goroutine 27 [chan receive]:\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.00997664+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"example.com/deeptimeout.TestHoldsTheSeat.func1()\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.00998035+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x19\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.00998418+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"created by example.com/deeptimeout.TestHoldsTheSeat in goroutine 7\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.00998793+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x37\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.00999131+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.00999534+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"goroutine 28 [chan receive]:\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.00999894+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"example.com/deeptimeout.TestHoldsTheSeat.func1()\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.01000268+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x19\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.0100087+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"created by example.com/deeptimeout.TestHoldsTheSeat in goroutine 7\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.01001258+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x37\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.01001605+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.01002007+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"goroutine 29 [chan receive]:\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.01002393+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"example.com/deeptimeout.TestHoldsTheSeat.func1()\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010027531+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x19\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010031351+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"created by example.com/deeptimeout.TestHoldsTheSeat in goroutine 7\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010035521+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x37\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010038781+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010042281+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"goroutine 30 [chan receive]:\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010046091+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"example.com/deeptimeout.TestHoldsTheSeat.func1()\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010054261+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x19\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010057881+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"created by example.com/deeptimeout.TestHoldsTheSeat in goroutine 7\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010061781+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x37\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010064931+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010068261+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"goroutine 31 [chan receive]:\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010072151+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"example.com/deeptimeout.TestHoldsTheSeat.func1()\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010075981+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x19\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010079841+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"created by example.com/deeptimeout.TestHoldsTheSeat in goroutine 7\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010098802+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x37\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010103202+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010106382+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"goroutine 32 [chan receive]:\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010109852+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"example.com/deeptimeout.TestHoldsTheSeat.func1()\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010113242+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x19\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010116952+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"created by example.com/deeptimeout.TestHoldsTheSeat in goroutine 7\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010120672+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x37\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010124112+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010127162+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"goroutine 33 [chan receive]:\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010130662+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"example.com/deeptimeout.TestHoldsTheSeat.func1()\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010134442+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x19\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010138142+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"created by example.com/deeptimeout.TestHoldsTheSeat in goroutine 7\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010141972+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x37\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010145362+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010152262+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"goroutine 34 [chan receive]:\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010156353+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"example.com/deeptimeout.TestHoldsTheSeat.func1()\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010159913+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x19\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010163533+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"created by example.com/deeptimeout.TestHoldsTheSeat in goroutine 7\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010167403+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x37\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010170533+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010173953+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"goroutine 35 [chan receive]:\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010177453+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"example.com/deeptimeout.TestHoldsTheSeat.func1()\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010181033+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x19\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010184693+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"created by example.com/deeptimeout.TestHoldsTheSeat in goroutine 7\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010188583+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x37\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010191753+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010195033+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"goroutine 36 [chan receive]:\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010198603+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"example.com/deeptimeout.TestHoldsTheSeat.func1()\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010202393+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x19\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010206123+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"created by example.com/deeptimeout.TestHoldsTheSeat in goroutine 7\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010210583+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x37\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010213503+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010216914+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"goroutine 37 [chan receive]:\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010220134+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"example.com/deeptimeout.TestHoldsTheSeat.func1()\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010223644+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x19\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010227154+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"created by example.com/deeptimeout.TestHoldsTheSeat in goroutine 7\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010235154+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x37\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010238354+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010241754+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"goroutine 38 [chan receive]:\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010270284+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"example.com/deeptimeout.TestHoldsTheSeat.func1()\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010275144+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x19\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010278714+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"created by example.com/deeptimeout.TestHoldsTheSeat in goroutine 7\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010282825+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x37\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010285795+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010288935+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"goroutine 39 [chan receive]:\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010292405+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"example.com/deeptimeout.TestHoldsTheSeat.func1()\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010295915+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x19\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010299545+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"created by example.com/deeptimeout.TestHoldsTheSeat in goroutine 7\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010311835+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x37\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010315565+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010318975+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"goroutine 40 [chan receive]:\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010322655+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"example.com/deeptimeout.TestHoldsTheSeat.func1()\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010326225+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x19\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010329815+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"created by example.com/deeptimeout.TestHoldsTheSeat in goroutine 7\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010333745+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x37\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010337095+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010340515+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"goroutine 41 [chan receive]:\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010344066+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"example.com/deeptimeout.TestHoldsTheSeat.func1()\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010347706+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x19\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010355596+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"created by example.com/deeptimeout.TestHoldsTheSeat in goroutine 7\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010359046+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x37\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010362036+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010365076+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"goroutine 42 [chan receive]:\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010367956+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"example.com/deeptimeout.TestHoldsTheSeat.func1()\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010370906+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x19\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010375366+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"created by example.com/deeptimeout.TestHoldsTheSeat in goroutine 7\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010378386+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x37\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010381006+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010383666+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"goroutine 43 [chan receive]:\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010386676+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"example.com/deeptimeout.TestHoldsTheSeat.func1()\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010389786+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x19\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010392636+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"created by example.com/deeptimeout.TestHoldsTheSeat in goroutine 7\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010395746+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x37\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010398466+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010401166+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"goroutine 44 [chan receive]:\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010404226+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"example.com/deeptimeout.TestHoldsTheSeat.func1()\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010407186+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x19\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010410117+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"created by example.com/deeptimeout.TestHoldsTheSeat in goroutine 7\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010413237+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x37\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010416157+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010418897+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"goroutine 45 [chan receive]:\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010423157+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"example.com/deeptimeout.TestHoldsTheSeat.func1()\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010430107+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x19\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010433197+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"created by example.com/deeptimeout.TestHoldsTheSeat in goroutine 7\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010451207+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x37\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010454217+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010456967+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"goroutine 46 [chan receive]:\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010459877+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"example.com/deeptimeout.TestHoldsTheSeat.func1()\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010463967+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x19\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010467067+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"created by example.com/deeptimeout.TestHoldsTheSeat in goroutine 7\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010470597+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x37\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010473418+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010476108+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"goroutine 47 [chan receive]:\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010478998+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"example.com/deeptimeout.TestHoldsTheSeat.func1()\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010481958+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x19\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010484978+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"created by example.com/deeptimeout.TestHoldsTheSeat in goroutine 7\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010488258+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x37\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010490888+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010493628+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"goroutine 48 [chan receive]:\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010496588+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"example.com/deeptimeout.TestHoldsTheSeat.func1()\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010499498+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x19\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010502498+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"created by example.com/deeptimeout.TestHoldsTheSeat in goroutine 7\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010505768+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x37\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010508558+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010514148+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"goroutine 49 [chan receive]:\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010517178+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"example.com/deeptimeout.TestHoldsTheSeat.func1()\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010520238+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x19\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010523478+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"created by example.com/deeptimeout.TestHoldsTheSeat in goroutine 7\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010526818+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x37\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010529718+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010532508+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"goroutine 50 [chan receive]:\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010535639+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"example.com/deeptimeout.TestHoldsTheSeat.func1()\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010538749+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x19\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010541939+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"created by example.com/deeptimeout.TestHoldsTheSeat in goroutine 7\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010546689+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x37\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010549869+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010552989+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"goroutine 51 [chan receive]:\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010556299+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"example.com/deeptimeout.TestHoldsTheSeat.func1()\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010560079+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x19\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010563479+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"created by example.com/deeptimeout.TestHoldsTheSeat in goroutine 7\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010567189+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x37\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010570299+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010573079+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"goroutine 52 [chan receive]:\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010576319+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"example.com/deeptimeout.TestHoldsTheSeat.func1()\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010579599+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x19\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010583099+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"created by example.com/deeptimeout.TestHoldsTheSeat in goroutine 7\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010590519+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x37\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010594029+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010596989+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"goroutine 53 [chan receive]:\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.01059986+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"example.com/deeptimeout.TestHoldsTheSeat.func1()\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.01060433+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x19\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.01060789+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"created by example.com/deeptimeout.TestHoldsTheSeat in goroutine 7\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.01061136+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x37\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.01061393+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.01061667+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"goroutine 54 [chan receive]:\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.01061984+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"example.com/deeptimeout.TestHoldsTheSeat.func1()\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.0106227+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x19\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.01062566+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"created by example.com/deeptimeout.TestHoldsTheSeat in goroutine 7\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.01062876+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x37\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.01063131+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.01063428+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"goroutine 55 [chan receive]:\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.01063719+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"example.com/deeptimeout.TestHoldsTheSeat.func1()\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.01063997+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x19\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.01064278+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"created by example.com/deeptimeout.TestHoldsTheSeat in goroutine 7\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.01064582+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x37\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.01064836+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.01065114+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"goroutine 56 [chan receive]:\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.01065437+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"example.com/deeptimeout.TestHoldsTheSeat.func1()\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.01065724+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x19\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010671481+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"created by example.com/deeptimeout.TestHoldsTheSeat in goroutine 7\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010675671+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x37\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010678431+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010691171+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"goroutine 57 [chan receive]:\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010694531+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"example.com/deeptimeout.TestHoldsTheSeat.func1()\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010697641+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x19\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010700781+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"created by example.com/deeptimeout.TestHoldsTheSeat in goroutine 7\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010704001+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x37\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010706611+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010709421+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"goroutine 58 [chan receive]:\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010712331+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"example.com/deeptimeout.TestHoldsTheSeat.func1()\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010715351+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x19\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010718431+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"created by example.com/deeptimeout.TestHoldsTheSeat in goroutine 7\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010721691+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x37\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010724371+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010727362+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"goroutine 59 [chan receive]:\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010730592+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"example.com/deeptimeout.TestHoldsTheSeat.func1()\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010733662+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x19\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010736712+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"created by example.com/deeptimeout.TestHoldsTheSeat in goroutine 7\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010740162+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x37\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010742922+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010745772+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"goroutine 60 [chan receive]:\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010752222+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"example.com/deeptimeout.TestHoldsTheSeat.func1()\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010755402+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x19\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010758542+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"created by example.com/deeptimeout.TestHoldsTheSeat in goroutine 7\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010761822+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x37\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010764592+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010767992+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"goroutine 61 [chan receive]:\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010771062+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"example.com/deeptimeout.TestHoldsTheSeat.func1()\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010774282+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x19\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010779382+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"created by example.com/deeptimeout.TestHoldsTheSeat in goroutine 7\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010782942+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x37\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010785722+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010788573+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"goroutine 62 [chan receive]:\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010791893+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"example.com/deeptimeout.TestHoldsTheSeat.func1()\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010794943+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x19\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010798083+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"created by example.com/deeptimeout.TestHoldsTheSeat in goroutine 7\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010801683+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x37\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010804293+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010807283+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"goroutine 63 [chan receive]:\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010810443+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"example.com/deeptimeout.TestHoldsTheSeat.func1()\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010813553+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x19\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010816793+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"created by example.com/deeptimeout.TestHoldsTheSeat in goroutine 7\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010820233+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x37\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010823063+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010829493+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"goroutine 64 [chan receive]:\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010832683+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"example.com/deeptimeout.TestHoldsTheSeat.func1()\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010835953+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x19\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010839223+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"created by example.com/deeptimeout.TestHoldsTheSeat in goroutine 7\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010842463+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x37\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010845333+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010848423+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"goroutine 65 [chan receive]:\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010851663+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"example.com/deeptimeout.TestHoldsTheSeat.func1()\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010854964+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x19\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010858364+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"created by example.com/deeptimeout.TestHoldsTheSeat in goroutine 7\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010861714+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x37\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010864914+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010867984+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"goroutine 66 [chan receive]:\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010870944+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"example.com/deeptimeout.TestHoldsTheSeat.func1()\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010874114+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x19\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010877464+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"created by example.com/deeptimeout.TestHoldsTheSeat in goroutine 7\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010880504+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x37\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010883144+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010886194+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"goroutine 67 [chan receive]:\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010889154+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"example.com/deeptimeout.TestHoldsTheSeat.func1()\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010892234+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x19\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010895524+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"created by example.com/deeptimeout.TestHoldsTheSeat in goroutine 7\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010902154+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x37\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010904984+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010907984+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"goroutine 68 [chan receive]:\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010911064+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"example.com/deeptimeout.TestHoldsTheSeat.func1()\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010914074+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x19\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010917365+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"created by example.com/deeptimeout.TestHoldsTheSeat in goroutine 7\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.010920505+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x37\n"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.013209581+01:00","Action":"output","Package":"example.com/deeptimeout","Output":"FAIL\texample.com/deeptimeout\t2.021s\n","OutputType":"frame"}
|
|
||||||
{"Time":"2026-10-25T15:35:21.013258851+01:00","Action":"fail","Package":"example.com/deeptimeout","Elapsed":2.022}
|
|
||||||
-10
@@ -1,10 +0,0 @@
|
|||||||
{"Time":"2026-10-25T15:34:33.768742837+01:00","Action":"start","Package":"example.com/exiting"}
|
|
||||||
{"Time":"2026-10-25T15:34:33.773822287+01:00","Action":"run","Package":"example.com/exiting","Test":"TestFine"}
|
|
||||||
{"Time":"2026-10-25T15:34:33.773896608+01:00","Action":"output","Package":"example.com/exiting","Test":"TestFine","Output":"=== RUN TestFine\n","OutputType":"frame"}
|
|
||||||
{"Time":"2026-10-25T15:34:33.773940319+01:00","Action":"output","Package":"example.com/exiting","Test":"TestFine","Output":"--- PASS: TestFine (0.00s)\n","OutputType":"frame"}
|
|
||||||
{"Time":"2026-10-25T15:34:33.773945359+01:00","Action":"pass","Package":"example.com/exiting","Test":"TestFine","Elapsed":0}
|
|
||||||
{"Time":"2026-10-25T15:34:33.773952529+01:00","Action":"run","Package":"example.com/exiting","Test":"TestExits"}
|
|
||||||
{"Time":"2026-10-25T15:34:33.773956439+01:00","Action":"output","Package":"example.com/exiting","Test":"TestExits","Output":"=== RUN TestExits\n","OutputType":"frame"}
|
|
||||||
{"Time":"2026-10-25T15:34:33.773959829+01:00","Action":"output","Package":"example.com/exiting","Test":"TestExits","Output":" p_test.go:11: closing the store\n"}
|
|
||||||
{"Time":"2026-10-25T15:34:33.775158538+01:00","Action":"output","Package":"example.com/exiting","Output":"FAIL\texample.com/exiting\t0.006s\n","OutputType":"frame"}
|
|
||||||
{"Time":"2026-10-25T15:34:33.775186509+01:00","Action":"fail","Package":"example.com/exiting","Elapsed":0.006}
|
|
||||||
-7
@@ -1,7 +0,0 @@
|
|||||||
{"Time":"2026-10-25T15:34:34.328258934+01:00","Action":"start","Package":"example.com/killed"}
|
|
||||||
{"Time":"2026-10-25T15:34:34.331465435+01:00","Action":"run","Package":"example.com/killed","Test":"TestKilled"}
|
|
||||||
{"Time":"2026-10-25T15:34:34.331699248+01:00","Action":"output","Package":"example.com/killed","Test":"TestKilled","Output":"=== RUN TestKilled\n","OutputType":"frame"}
|
|
||||||
{"Time":"2026-10-25T15:34:34.331853111+01:00","Action":"output","Package":"example.com/killed","Test":"TestKilled","Output":" p_test.go:10: the kernel is about to kill this\n"}
|
|
||||||
{"Time":"2026-10-25T15:34:34.334563213+01:00","Action":"output","Package":"example.com/killed","Test":"TestKilled","Output":"signal: killed\n"}
|
|
||||||
{"Time":"2026-10-25T15:34:34.334611114+01:00","Action":"output","Package":"example.com/killed","Output":"FAIL\texample.com/killed\t0.006s\n","OutputType":"frame"}
|
|
||||||
{"Time":"2026-10-25T15:34:34.334627134+01:00","Action":"fail","Package":"example.com/killed","Elapsed":0.006}
|
|
||||||
-8
@@ -1,8 +0,0 @@
|
|||||||
{"Time":"2026-10-25T15:34:35.274953775+01:00","Action":"start","Package":"example.com/mainexit"}
|
|
||||||
{"Time":"2026-10-25T15:34:35.278129565+01:00","Action":"run","Package":"example.com/mainexit","Test":"TestFine"}
|
|
||||||
{"Time":"2026-10-25T15:34:35.278191516+01:00","Action":"output","Package":"example.com/mainexit","Test":"TestFine","Output":"=== RUN TestFine\n","OutputType":"frame"}
|
|
||||||
{"Time":"2026-10-25T15:34:35.278341979+01:00","Action":"output","Package":"example.com/mainexit","Test":"TestFine","Output":"--- PASS: TestFine (0.00s)\n","OutputType":"frame"}
|
|
||||||
{"Time":"2026-10-25T15:34:35.278351739+01:00","Action":"pass","Package":"example.com/mainexit","Test":"TestFine","Elapsed":0}
|
|
||||||
{"Time":"2026-10-25T15:34:35.278359039+01:00","Action":"output","Package":"example.com/mainexit","Output":"PASS\n","OutputType":"frame"}
|
|
||||||
{"Time":"2026-10-25T15:34:35.279516487+01:00","Action":"output","Package":"example.com/mainexit","Output":"FAIL\texample.com/mainexit\t0.004s\n","OutputType":"frame"}
|
|
||||||
{"Time":"2026-10-25T15:34:35.279539298+01:00","Action":"fail","Package":"example.com/mainexit","Elapsed":0.005}
|
|
||||||
-27
@@ -1,27 +0,0 @@
|
|||||||
{"Time":"2026-10-25T15:34:33.031091436+01:00","Action":"start","Package":"example.com/panicking"}
|
|
||||||
{"Time":"2026-10-25T15:34:33.036353279+01:00","Action":"run","Package":"example.com/panicking","Test":"TestBefore"}
|
|
||||||
{"Time":"2026-10-25T15:34:33.036441781+01:00","Action":"output","Package":"example.com/panicking","Test":"TestBefore","Output":"=== RUN TestBefore\n","OutputType":"frame"}
|
|
||||||
{"Time":"2026-10-25T15:34:33.036568163+01:00","Action":"output","Package":"example.com/panicking","Test":"TestBefore","Output":"--- PASS: TestBefore (0.00s)\n","OutputType":"frame"}
|
|
||||||
{"Time":"2026-10-25T15:34:33.036609863+01:00","Action":"pass","Package":"example.com/panicking","Test":"TestBefore","Elapsed":0}
|
|
||||||
{"Time":"2026-10-25T15:34:33.036641304+01:00","Action":"run","Package":"example.com/panicking","Test":"TestPanics"}
|
|
||||||
{"Time":"2026-10-25T15:34:33.036645914+01:00","Action":"output","Package":"example.com/panicking","Test":"TestPanics","Output":"=== RUN TestPanics\n","OutputType":"frame"}
|
|
||||||
{"Time":"2026-10-25T15:34:33.036698995+01:00","Action":"output","Package":"example.com/panicking","Test":"TestPanics","Output":" p_test.go:8: about to look up the seat\n"}
|
|
||||||
{"Time":"2026-10-25T15:34:33.036773996+01:00","Action":"output","Package":"example.com/panicking","Test":"TestPanics","Output":"--- FAIL: TestPanics (0.00s)\n","OutputType":"frame"}
|
|
||||||
{"Time":"2026-10-25T15:34:33.03897418+01:00","Action":"output","Package":"example.com/panicking","Test":"TestPanics","Output":"panic: assignment to entry in nil map [recovered, repanicked]\n"}
|
|
||||||
{"Time":"2026-10-25T15:34:33.040332172+01:00","Action":"output","Package":"example.com/panicking","Test":"TestPanics","Output":"\n"}
|
|
||||||
{"Time":"2026-10-25T15:34:33.040414493+01:00","Action":"output","Package":"example.com/panicking","Test":"TestPanics","Output":"goroutine 8 [running]:\n"}
|
|
||||||
{"Time":"2026-10-25T15:34:33.040521475+01:00","Action":"output","Package":"example.com/panicking","Test":"TestPanics","Output":"testing.tRunner.func1.2({0x6b8120, 0x6eff80})\n"}
|
|
||||||
{"Time":"2026-10-25T15:34:33.040601826+01:00","Action":"output","Package":"example.com/panicking","Test":"TestPanics","Output":"\t/usr/lib/go/src/testing/testing.go:2125 +0x232\n"}
|
|
||||||
{"Time":"2026-10-25T15:34:33.040641947+01:00","Action":"output","Package":"example.com/panicking","Test":"TestPanics","Output":"testing.tRunner.func1()\n"}
|
|
||||||
{"Time":"2026-10-25T15:34:33.040720648+01:00","Action":"output","Package":"example.com/panicking","Test":"TestPanics","Output":"\t/usr/lib/go/src/testing/testing.go:2128 +0x329\n"}
|
|
||||||
{"Time":"2026-10-25T15:34:33.04083286+01:00","Action":"output","Package":"example.com/panicking","Test":"TestPanics","Output":"panic({0x6b8120?, 0x6eff80?})\n"}
|
|
||||||
{"Time":"2026-10-25T15:34:33.040912921+01:00","Action":"output","Package":"example.com/panicking","Test":"TestPanics","Output":"\t/usr/lib/go/src/runtime/panic.go:859 +0x125\n"}
|
|
||||||
{"Time":"2026-10-25T15:34:33.040981652+01:00","Action":"output","Package":"example.com/panicking","Test":"TestPanics","Output":"example.com/panicking.TestPanics(0x228226cd0488?)\n"}
|
|
||||||
{"Time":"2026-10-25T15:34:33.041059903+01:00","Action":"output","Package":"example.com/panicking","Test":"TestPanics","Output":"\t/tmp/fix-1901b/panicking/p_test.go:10 +0x53\n"}
|
|
||||||
{"Time":"2026-10-25T15:34:33.041132704+01:00","Action":"output","Package":"example.com/panicking","Test":"TestPanics","Output":"testing.tRunner(0x228226cd0488, 0x6d5bc8)\n"}
|
|
||||||
{"Time":"2026-10-25T15:34:33.041215586+01:00","Action":"output","Package":"example.com/panicking","Test":"TestPanics","Output":"\t/usr/lib/go/src/testing/testing.go:2195 +0xea\n"}
|
|
||||||
{"Time":"2026-10-25T15:34:33.041221966+01:00","Action":"output","Package":"example.com/panicking","Test":"TestPanics","Output":"created by testing.(*T).Run in goroutine 1\n"}
|
|
||||||
{"Time":"2026-10-25T15:34:33.041225556+01:00","Action":"output","Package":"example.com/panicking","Test":"TestPanics","Output":"\t/usr/lib/go/src/testing/testing.go:2260 +0x4d4\n"}
|
|
||||||
{"Time":"2026-10-25T15:34:33.041984088+01:00","Action":"fail","Package":"example.com/panicking","Test":"TestPanics","Elapsed":0}
|
|
||||||
{"Time":"2026-10-25T15:34:33.041996638+01:00","Action":"output","Package":"example.com/panicking","Output":"FAIL\texample.com/panicking\t0.010s\n","OutputType":"frame"}
|
|
||||||
{"Time":"2026-10-25T15:34:33.042044079+01:00","Action":"fail","Package":"example.com/panicking","Elapsed":0.011}
|
|
||||||
Vendored
-15
@@ -1,15 +0,0 @@
|
|||||||
{"Time":"2026-10-25T15:34:34.919624192+01:00","Action":"start","Package":"example.com/passthenfail"}
|
|
||||||
{"Time":"2026-10-25T15:34:34.922861224+01:00","Action":"run","Package":"example.com/passthenfail","Test":"TestFine"}
|
|
||||||
{"Time":"2026-10-25T15:34:34.922932965+01:00","Action":"output","Package":"example.com/passthenfail","Test":"TestFine","Output":"=== RUN TestFine\n","OutputType":"frame"}
|
|
||||||
{"Time":"2026-10-25T15:34:34.922969205+01:00","Action":"output","Package":"example.com/passthenfail","Test":"TestFine","Output":"--- PASS: TestFine (0.00s)\n","OutputType":"frame"}
|
|
||||||
{"Time":"2026-10-25T15:34:34.922979956+01:00","Action":"pass","Package":"example.com/passthenfail","Test":"TestFine","Elapsed":0}
|
|
||||||
{"Time":"2026-10-25T15:34:34.922989416+01:00","Action":"output","Package":"example.com/passthenfail","Output":"PASS\n","OutputType":"frame"}
|
|
||||||
{"Time":"2026-10-25T15:34:34.925022528+01:00","Action":"output","Package":"example.com/passthenfail","Output":"panic: the bus closed after the tests: nats: connection closed\n"}
|
|
||||||
{"Time":"2026-10-25T15:34:34.925043388+01:00","Action":"output","Package":"example.com/passthenfail","Output":"\n"}
|
|
||||||
{"Time":"2026-10-25T15:34:34.925046848+01:00","Action":"output","Package":"example.com/passthenfail","Output":"goroutine 20 [running]:\n"}
|
|
||||||
{"Time":"2026-10-25T15:34:34.925051338+01:00","Action":"output","Package":"example.com/passthenfail","Output":"example.com/passthenfail.TestMain.func1()\n"}
|
|
||||||
{"Time":"2026-10-25T15:34:34.925055878+01:00","Action":"output","Package":"example.com/passthenfail","Output":"\t/tmp/fix-1901b/passthenfail/p_test.go:13 +0x25\n"}
|
|
||||||
{"Time":"2026-10-25T15:34:34.925061428+01:00","Action":"output","Package":"example.com/passthenfail","Output":"created by example.com/passthenfail.TestMain in goroutine 1\n"}
|
|
||||||
{"Time":"2026-10-25T15:34:34.925065028+01:00","Action":"output","Package":"example.com/passthenfail","Output":"\t/tmp/fix-1901b/passthenfail/p_test.go:13 +0x25\n"}
|
|
||||||
{"Time":"2026-10-25T15:34:34.925390203+01:00","Action":"output","Package":"example.com/passthenfail","Output":"FAIL\texample.com/passthenfail\t0.005s\n","OutputType":"frame"}
|
|
||||||
{"Time":"2026-10-25T15:34:34.925402904+01:00","Action":"fail","Package":"example.com/passthenfail","Elapsed":0.006}
|
|
||||||
-53
@@ -1,53 +0,0 @@
|
|||||||
{"Time":"2026-10-25T15:50:42.908557309+01:00","Action":"start","Package":"example.com/race"}
|
|
||||||
{"Time":"2026-10-25T15:50:42.924298679+01:00","Action":"run","Package":"example.com/race","Test":"TestBefore"}
|
|
||||||
{"Time":"2026-10-25T15:50:42.92438195+01:00","Action":"output","Package":"example.com/race","Test":"TestBefore","Output":"=== RUN TestBefore\n","OutputType":"frame"}
|
|
||||||
{"Time":"2026-10-25T15:50:42.924648842+01:00","Action":"output","Package":"example.com/race","Test":"TestBefore","Output":"--- PASS: TestBefore (0.00s)\n","OutputType":"frame"}
|
|
||||||
{"Time":"2026-10-25T15:50:42.924685133+01:00","Action":"pass","Package":"example.com/race","Test":"TestBefore","Elapsed":0}
|
|
||||||
{"Time":"2026-10-25T15:50:42.924728573+01:00","Action":"run","Package":"example.com/race","Test":"TestRacesOnTheSeat"}
|
|
||||||
{"Time":"2026-10-25T15:50:42.924733573+01:00","Action":"output","Package":"example.com/race","Test":"TestRacesOnTheSeat","Output":"=== RUN TestRacesOnTheSeat\n","OutputType":"frame"}
|
|
||||||
{"Time":"2026-10-25T15:50:42.92637298+01:00","Action":"output","Package":"example.com/race","Test":"TestRacesOnTheSeat","Output":"==================\n"}
|
|
||||||
{"Time":"2026-10-25T15:50:42.92641567+01:00","Action":"output","Package":"example.com/race","Test":"TestRacesOnTheSeat","Output":"WARNING: DATA RACE\n"}
|
|
||||||
{"Time":"2026-10-25T15:50:42.926444061+01:00","Action":"output","Package":"example.com/race","Test":"TestRacesOnTheSeat","Output":"Write at 0x000000833468 by goroutine 10:\n"}
|
|
||||||
{"Time":"2026-10-25T15:50:42.926462621+01:00","Action":"output","Package":"example.com/race","Test":"TestRacesOnTheSeat","Output":" example.com/race.TestRacesOnTheSeat.func1()\n"}
|
|
||||||
{"Time":"2026-10-25T15:50:42.926467121+01:00","Action":"output","Package":"example.com/race","Test":"TestRacesOnTheSeat","Output":" /tmp/fix-1901b/race/p_test.go:13 +0x24\n"}
|
|
||||||
{"Time":"2026-10-25T15:50:42.926483061+01:00","Action":"output","Package":"example.com/race","Test":"TestRacesOnTheSeat","Output":"\n"}
|
|
||||||
{"Time":"2026-10-25T15:50:42.926509741+01:00","Action":"output","Package":"example.com/race","Test":"TestRacesOnTheSeat","Output":"Previous write at 0x000000833468 by goroutine 9:\n"}
|
|
||||||
{"Time":"2026-10-25T15:50:42.926530142+01:00","Action":"output","Package":"example.com/race","Test":"TestRacesOnTheSeat","Output":" example.com/race.TestRacesOnTheSeat()\n"}
|
|
||||||
{"Time":"2026-10-25T15:50:42.926534952+01:00","Action":"output","Package":"example.com/race","Test":"TestRacesOnTheSeat","Output":" /tmp/fix-1901b/race/p_test.go:14 +0x30\n"}
|
|
||||||
{"Time":"2026-10-25T15:50:42.926550462+01:00","Action":"output","Package":"example.com/race","Test":"TestRacesOnTheSeat","Output":" testing.tRunner()\n"}
|
|
||||||
{"Time":"2026-10-25T15:50:42.926554612+01:00","Action":"output","Package":"example.com/race","Test":"TestRacesOnTheSeat","Output":" /usr/lib/go/src/testing/testing.go:2195 +0x21c\n"}
|
|
||||||
{"Time":"2026-10-25T15:50:42.926571142+01:00","Action":"output","Package":"example.com/race","Test":"TestRacesOnTheSeat","Output":" testing.(*T).Run.gowrap1()\n"}
|
|
||||||
{"Time":"2026-10-25T15:50:42.926575812+01:00","Action":"output","Package":"example.com/race","Test":"TestRacesOnTheSeat","Output":" /usr/lib/go/src/testing/testing.go:2260 +0x38\n"}
|
|
||||||
{"Time":"2026-10-25T15:50:42.926592202+01:00","Action":"output","Package":"example.com/race","Test":"TestRacesOnTheSeat","Output":"\n"}
|
|
||||||
{"Time":"2026-10-25T15:50:42.926607792+01:00","Action":"output","Package":"example.com/race","Test":"TestRacesOnTheSeat","Output":"Goroutine 10 (running) created at:\n"}
|
|
||||||
{"Time":"2026-10-25T15:50:42.926624292+01:00","Action":"output","Package":"example.com/race","Test":"TestRacesOnTheSeat","Output":" example.com/race.TestRacesOnTheSeat()\n"}
|
|
||||||
{"Time":"2026-10-25T15:50:42.926628923+01:00","Action":"output","Package":"example.com/race","Test":"TestRacesOnTheSeat","Output":" /tmp/fix-1901b/race/p_test.go:13 +0x24\n"}
|
|
||||||
{"Time":"2026-10-25T15:50:42.926643853+01:00","Action":"output","Package":"example.com/race","Test":"TestRacesOnTheSeat","Output":" testing.tRunner()\n"}
|
|
||||||
{"Time":"2026-10-25T15:50:42.926648103+01:00","Action":"output","Package":"example.com/race","Test":"TestRacesOnTheSeat","Output":" /usr/lib/go/src/testing/testing.go:2195 +0x21c\n"}
|
|
||||||
{"Time":"2026-10-25T15:50:42.926674843+01:00","Action":"output","Package":"example.com/race","Test":"TestRacesOnTheSeat","Output":" testing.(*T).Run.gowrap1()\n"}
|
|
||||||
{"Time":"2026-10-25T15:50:42.926680673+01:00","Action":"output","Package":"example.com/race","Test":"TestRacesOnTheSeat","Output":" /usr/lib/go/src/testing/testing.go:2260 +0x38\n"}
|
|
||||||
{"Time":"2026-10-25T15:50:42.926695433+01:00","Action":"output","Package":"example.com/race","Test":"TestRacesOnTheSeat","Output":"\n"}
|
|
||||||
{"Time":"2026-10-25T15:50:42.926711063+01:00","Action":"output","Package":"example.com/race","Test":"TestRacesOnTheSeat","Output":"Goroutine 9 (running) created at:\n"}
|
|
||||||
{"Time":"2026-10-25T15:50:42.926726614+01:00","Action":"output","Package":"example.com/race","Test":"TestRacesOnTheSeat","Output":" testing.(*T).Run()\n"}
|
|
||||||
{"Time":"2026-10-25T15:50:42.926730814+01:00","Action":"output","Package":"example.com/race","Test":"TestRacesOnTheSeat","Output":" /usr/lib/go/src/testing/testing.go:2260 +0xb12\n"}
|
|
||||||
{"Time":"2026-10-25T15:50:42.926746484+01:00","Action":"output","Package":"example.com/race","Test":"TestRacesOnTheSeat","Output":" testing.runTests.func1()\n"}
|
|
||||||
{"Time":"2026-10-25T15:50:42.926750764+01:00","Action":"output","Package":"example.com/race","Test":"TestRacesOnTheSeat","Output":" /usr/lib/go/src/testing/testing.go:2744 +0x84\n"}
|
|
||||||
{"Time":"2026-10-25T15:50:42.926765354+01:00","Action":"output","Package":"example.com/race","Test":"TestRacesOnTheSeat","Output":" testing.tRunner()\n"}
|
|
||||||
{"Time":"2026-10-25T15:50:42.926769844+01:00","Action":"output","Package":"example.com/race","Test":"TestRacesOnTheSeat","Output":" /usr/lib/go/src/testing/testing.go:2195 +0x21c\n"}
|
|
||||||
{"Time":"2026-10-25T15:50:42.926791794+01:00","Action":"output","Package":"example.com/race","Test":"TestRacesOnTheSeat","Output":" testing.runTests()\n"}
|
|
||||||
{"Time":"2026-10-25T15:50:42.926796704+01:00","Action":"output","Package":"example.com/race","Test":"TestRacesOnTheSeat","Output":" /usr/lib/go/src/testing/testing.go:2742 +0x9e9\n"}
|
|
||||||
{"Time":"2026-10-25T15:50:42.926811544+01:00","Action":"output","Package":"example.com/race","Test":"TestRacesOnTheSeat","Output":" testing.(*M).Run()\n"}
|
|
||||||
{"Time":"2026-10-25T15:50:42.926816694+01:00","Action":"output","Package":"example.com/race","Test":"TestRacesOnTheSeat","Output":" /usr/lib/go/src/testing/testing.go:2602 +0xf44\n"}
|
|
||||||
{"Time":"2026-10-25T15:50:42.926831695+01:00","Action":"output","Package":"example.com/race","Test":"TestRacesOnTheSeat","Output":" main.main()\n"}
|
|
||||||
{"Time":"2026-10-25T15:50:42.926835935+01:00","Action":"output","Package":"example.com/race","Test":"TestRacesOnTheSeat","Output":" _testmain.go:50 +0x164\n"}
|
|
||||||
{"Time":"2026-10-25T15:50:42.926850905+01:00","Action":"output","Package":"example.com/race","Test":"TestRacesOnTheSeat","Output":"==================\n"}
|
|
||||||
{"Time":"2026-10-25T15:50:42.935320241+01:00","Action":"output","Package":"example.com/race","Test":"TestRacesOnTheSeat","Output":" testing.go:1867: race detected during execution of test\n","OutputType":"error"}
|
|
||||||
{"Time":"2026-10-25T15:50:42.935520113+01:00","Action":"output","Package":"example.com/race","Test":"TestRacesOnTheSeat","Output":"--- FAIL: TestRacesOnTheSeat (0.01s)\n","OutputType":"frame"}
|
|
||||||
{"Time":"2026-10-25T15:50:42.935592973+01:00","Action":"fail","Package":"example.com/race","Test":"TestRacesOnTheSeat","Elapsed":0.01}
|
|
||||||
{"Time":"2026-10-25T15:50:42.935722225+01:00","Action":"run","Package":"example.com/race","Test":"TestAfter"}
|
|
||||||
{"Time":"2026-10-25T15:50:42.935728845+01:00","Action":"output","Package":"example.com/race","Test":"TestAfter","Output":"=== RUN TestAfter\n","OutputType":"frame"}
|
|
||||||
{"Time":"2026-10-25T15:50:42.935995928+01:00","Action":"output","Package":"example.com/race","Test":"TestAfter","Output":"--- PASS: TestAfter (0.00s)\n","OutputType":"frame"}
|
|
||||||
{"Time":"2026-10-25T15:50:42.936006328+01:00","Action":"pass","Package":"example.com/race","Test":"TestAfter","Elapsed":0}
|
|
||||||
{"Time":"2026-10-25T15:50:42.936009978+01:00","Action":"output","Package":"example.com/race","Output":"FAIL\n","OutputType":"frame"}
|
|
||||||
{"Time":"2026-10-25T15:50:42.937730745+01:00","Action":"output","Package":"example.com/race","Output":"FAIL\texample.com/race\t0.028s\n","OutputType":"frame"}
|
|
||||||
{"Time":"2026-10-25T15:50:42.937802066+01:00","Action":"fail","Package":"example.com/race","Elapsed":0.029}
|
|
||||||
-27
@@ -1,27 +0,0 @@
|
|||||||
{"Time":"2026-10-25T15:50:43.551701745+01:00","Action":"start","Package":"example.com/racemain"}
|
|
||||||
{"Time":"2026-10-25T15:50:43.570709708+01:00","Action":"run","Package":"example.com/racemain","Test":"TestFine"}
|
|
||||||
{"Time":"2026-10-25T15:50:43.570806059+01:00","Action":"output","Package":"example.com/racemain","Test":"TestFine","Output":"=== RUN TestFine\n","OutputType":"frame"}
|
|
||||||
{"Time":"2026-10-25T15:50:43.57085525+01:00","Action":"output","Package":"example.com/racemain","Test":"TestFine","Output":"--- PASS: TestFine (0.00s)\n","OutputType":"frame"}
|
|
||||||
{"Time":"2026-10-25T15:50:43.57086149+01:00","Action":"pass","Package":"example.com/racemain","Test":"TestFine","Elapsed":0}
|
|
||||||
{"Time":"2026-10-25T15:50:43.57086974+01:00","Action":"output","Package":"example.com/racemain","Output":"PASS\n","OutputType":"frame"}
|
|
||||||
{"Time":"2026-10-25T15:50:43.572155623+01:00","Action":"output","Package":"example.com/racemain","Output":"==================\n"}
|
|
||||||
{"Time":"2026-10-25T15:50:43.572168083+01:00","Action":"output","Package":"example.com/racemain","Output":"WARNING: DATA RACE\n"}
|
|
||||||
{"Time":"2026-10-25T15:50:43.572171933+01:00","Action":"output","Package":"example.com/racemain","Output":"Write at 0x000000836428 by goroutine 9:\n"}
|
|
||||||
{"Time":"2026-10-25T15:50:43.572176703+01:00","Action":"output","Package":"example.com/racemain","Output":" example.com/racemain.TestMain.func1()\n"}
|
|
||||||
{"Time":"2026-10-25T15:50:43.572180053+01:00","Action":"output","Package":"example.com/racemain","Output":" /tmp/fix-1901b/racemain/p_test.go:15 +0x24\n"}
|
|
||||||
{"Time":"2026-10-25T15:50:43.572184923+01:00","Action":"output","Package":"example.com/racemain","Output":"\n"}
|
|
||||||
{"Time":"2026-10-25T15:50:43.572189613+01:00","Action":"output","Package":"example.com/racemain","Output":"Previous write at 0x000000836428 by main goroutine:\n"}
|
|
||||||
{"Time":"2026-10-25T15:50:43.572192993+01:00","Action":"output","Package":"example.com/racemain","Output":" example.com/racemain.TestMain()\n"}
|
|
||||||
{"Time":"2026-10-25T15:50:43.572198573+01:00","Action":"output","Package":"example.com/racemain","Output":" /tmp/fix-1901b/racemain/p_test.go:16 +0x44\n"}
|
|
||||||
{"Time":"2026-10-25T15:50:43.572202433+01:00","Action":"output","Package":"example.com/racemain","Output":" main.main()\n"}
|
|
||||||
{"Time":"2026-10-25T15:50:43.572205623+01:00","Action":"output","Package":"example.com/racemain","Output":" _testmain.go:48 +0x171\n"}
|
|
||||||
{"Time":"2026-10-25T15:50:43.572208443+01:00","Action":"output","Package":"example.com/racemain","Output":"\n"}
|
|
||||||
{"Time":"2026-10-25T15:50:43.572211573+01:00","Action":"output","Package":"example.com/racemain","Output":"Goroutine 9 (running) created at:\n"}
|
|
||||||
{"Time":"2026-10-25T15:50:43.572214803+01:00","Action":"output","Package":"example.com/racemain","Output":" example.com/racemain.TestMain()\n"}
|
|
||||||
{"Time":"2026-10-25T15:50:43.572218043+01:00","Action":"output","Package":"example.com/racemain","Output":" /tmp/fix-1901b/racemain/p_test.go:15 +0x37\n"}
|
|
||||||
{"Time":"2026-10-25T15:50:43.572225414+01:00","Action":"output","Package":"example.com/racemain","Output":" main.main()\n"}
|
|
||||||
{"Time":"2026-10-25T15:50:43.572228654+01:00","Action":"output","Package":"example.com/racemain","Output":" _testmain.go:48 +0x171\n"}
|
|
||||||
{"Time":"2026-10-25T15:50:43.572231664+01:00","Action":"output","Package":"example.com/racemain","Output":"==================\n"}
|
|
||||||
{"Time":"2026-10-25T15:50:44.581262249+01:00","Action":"output","Package":"example.com/racemain","Output":"Found 1 data race(s)\n"}
|
|
||||||
{"Time":"2026-10-25T15:50:44.584693204+01:00","Action":"output","Package":"example.com/racemain","Output":"FAIL\texample.com/racemain\t1.031s\n","OutputType":"frame"}
|
|
||||||
{"Time":"2026-10-25T15:50:44.584737425+01:00","Action":"fail","Package":"example.com/racemain","Elapsed":1.033}
|
|
||||||
-43
@@ -1,43 +0,0 @@
|
|||||||
{"Time":"2026-10-25T15:34:36.049580618+01:00","Action":"start","Package":"example.com/timeout"}
|
|
||||||
{"Time":"2026-10-25T15:34:36.053250986+01:00","Action":"run","Package":"example.com/timeout","Test":"TestQuick"}
|
|
||||||
{"Time":"2026-10-25T15:34:36.053354018+01:00","Action":"output","Package":"example.com/timeout","Test":"TestQuick","Output":"=== RUN TestQuick\n","OutputType":"frame"}
|
|
||||||
{"Time":"2026-10-25T15:34:36.053459789+01:00","Action":"output","Package":"example.com/timeout","Test":"TestQuick","Output":"--- PASS: TestQuick (0.00s)\n","OutputType":"frame"}
|
|
||||||
{"Time":"2026-10-25T15:34:36.05348499+01:00","Action":"pass","Package":"example.com/timeout","Test":"TestQuick","Elapsed":0}
|
|
||||||
{"Time":"2026-10-25T15:34:36.0535114+01:00","Action":"run","Package":"example.com/timeout","Test":"TestWaitsForever"}
|
|
||||||
{"Time":"2026-10-25T15:34:36.05351549+01:00","Action":"output","Package":"example.com/timeout","Test":"TestWaitsForever","Output":"=== RUN TestWaitsForever\n","OutputType":"frame"}
|
|
||||||
{"Time":"2026-10-25T15:34:36.053567451+01:00","Action":"output","Package":"example.com/timeout","Test":"TestWaitsForever","Output":" p_test.go:11: waiting for the bus\n"}
|
|
||||||
{"Time":"2026-10-25T15:34:38.064693307+01:00","Action":"output","Package":"example.com/timeout","Test":"TestWaitsForever","Output":"panic: test timed out after 2s\n"}
|
|
||||||
{"Time":"2026-10-25T15:34:38.06487122+01:00","Action":"output","Package":"example.com/timeout","Test":"TestWaitsForever","Output":"\trunning tests:\n"}
|
|
||||||
{"Time":"2026-10-25T15:34:38.06491684+01:00","Action":"output","Package":"example.com/timeout","Test":"TestWaitsForever","Output":"\t\tTestWaitsForever (2s)\n"}
|
|
||||||
{"Time":"2026-10-25T15:34:38.065034382+01:00","Action":"output","Package":"example.com/timeout","Test":"TestWaitsForever","Output":"\n"}
|
|
||||||
{"Time":"2026-10-25T15:34:38.065108573+01:00","Action":"output","Package":"example.com/timeout","Test":"TestWaitsForever","Output":"goroutine 33 [running]:\n"}
|
|
||||||
{"Time":"2026-10-25T15:34:38.065154754+01:00","Action":"output","Package":"example.com/timeout","Test":"TestWaitsForever","Output":"testing.(*M).startAlarm.func1()\n"}
|
|
||||||
{"Time":"2026-10-25T15:34:38.065386208+01:00","Action":"output","Package":"example.com/timeout","Test":"TestWaitsForever","Output":"\t/usr/lib/go/src/testing/testing.go:2961 +0x34a\n"}
|
|
||||||
{"Time":"2026-10-25T15:34:38.065431878+01:00","Action":"output","Package":"example.com/timeout","Test":"TestWaitsForever","Output":"created by time.goFunc\n"}
|
|
||||||
{"Time":"2026-10-25T15:34:38.0655151+01:00","Action":"output","Package":"example.com/timeout","Test":"TestWaitsForever","Output":"\t/usr/lib/go/src/time/sleep.go:182 +0x2d\n"}
|
|
||||||
{"Time":"2026-10-25T15:34:38.06553471+01:00","Action":"output","Package":"example.com/timeout","Test":"TestWaitsForever","Output":"\n"}
|
|
||||||
{"Time":"2026-10-25T15:34:38.065847805+01:00","Action":"output","Package":"example.com/timeout","Test":"TestWaitsForever","Output":"goroutine 1 [chan receive]:\n"}
|
|
||||||
{"Time":"2026-10-25T15:34:38.066038298+01:00","Action":"output","Package":"example.com/timeout","Test":"TestWaitsForever","Output":"testing.(*T).Run(0x3a0209bc8008, {0x5580aa?, 0x3a0209a9caa0?}, 0x6d5b10)\n"}
|
|
||||||
{"Time":"2026-10-25T15:34:38.066047488+01:00","Action":"output","Package":"example.com/timeout","Test":"TestWaitsForever","Output":"\t/usr/lib/go/src/testing/testing.go:2268 +0x4f2\n"}
|
|
||||||
{"Time":"2026-10-25T15:34:38.066051368+01:00","Action":"output","Package":"example.com/timeout","Test":"TestWaitsForever","Output":"testing.runTests.func1(0x3a0209bc8008)\n"}
|
|
||||||
{"Time":"2026-10-25T15:34:38.066056128+01:00","Action":"output","Package":"example.com/timeout","Test":"TestWaitsForever","Output":"\t/usr/lib/go/src/testing/testing.go:2744 +0x37\n"}
|
|
||||||
{"Time":"2026-10-25T15:34:38.066059478+01:00","Action":"output","Package":"example.com/timeout","Test":"TestWaitsForever","Output":"testing.tRunner(0x3a0209bc8008, 0x3a0209a9cbc8)\n"}
|
|
||||||
{"Time":"2026-10-25T15:34:38.066064958+01:00","Action":"output","Package":"example.com/timeout","Test":"TestWaitsForever","Output":"\t/usr/lib/go/src/testing/testing.go:2195 +0xea\n"}
|
|
||||||
{"Time":"2026-10-25T15:34:38.066069599+01:00","Action":"output","Package":"example.com/timeout","Test":"TestWaitsForever","Output":"testing.runTests({0x559017, 0x13}, {0x559017, 0x13}, 0x3a0209b8e0d8, {0x6f19f0, 0x2, 0x2}, {0xc2afa2bf832a3f56, 0x773908b5, ...})\n"}
|
|
||||||
{"Time":"2026-10-25T15:34:38.066083309+01:00","Action":"output","Package":"example.com/timeout","Test":"TestWaitsForever","Output":"\t/usr/lib/go/src/testing/testing.go:2742 +0x510\n"}
|
|
||||||
{"Time":"2026-10-25T15:34:38.066086879+01:00","Action":"output","Package":"example.com/timeout","Test":"TestWaitsForever","Output":"testing.(*M).Run(0x3a0209b8c1e0)\n"}
|
|
||||||
{"Time":"2026-10-25T15:34:38.066090269+01:00","Action":"output","Package":"example.com/timeout","Test":"TestWaitsForever","Output":"\t/usr/lib/go/src/testing/testing.go:2602 +0x6af\n"}
|
|
||||||
{"Time":"2026-10-25T15:34:38.066093429+01:00","Action":"output","Package":"example.com/timeout","Test":"TestWaitsForever","Output":"main.main()\n"}
|
|
||||||
{"Time":"2026-10-25T15:34:38.066097569+01:00","Action":"output","Package":"example.com/timeout","Test":"TestWaitsForever","Output":"\t_testmain.go:48 +0x9b\n"}
|
|
||||||
{"Time":"2026-10-25T15:34:38.066100639+01:00","Action":"output","Package":"example.com/timeout","Test":"TestWaitsForever","Output":"\n"}
|
|
||||||
{"Time":"2026-10-25T15:34:38.066103769+01:00","Action":"output","Package":"example.com/timeout","Test":"TestWaitsForever","Output":"goroutine 20 [sleep]:\n"}
|
|
||||||
{"Time":"2026-10-25T15:34:38.066106969+01:00","Action":"output","Package":"example.com/timeout","Test":"TestWaitsForever","Output":"time.Sleep(0xdf8475800)\n"}
|
|
||||||
{"Time":"2026-10-25T15:34:38.066110279+01:00","Action":"output","Package":"example.com/timeout","Test":"TestWaitsForever","Output":"\t/usr/lib/go/src/runtime/time.go:368 +0x165\n"}
|
|
||||||
{"Time":"2026-10-25T15:34:38.066113639+01:00","Action":"output","Package":"example.com/timeout","Test":"TestWaitsForever","Output":"example.com/timeout.TestWaitsForever(0x3a0209bc8488?)\n"}
|
|
||||||
{"Time":"2026-10-25T15:34:38.066117109+01:00","Action":"output","Package":"example.com/timeout","Test":"TestWaitsForever","Output":"\t/tmp/fix-1901b/timeout/p_test.go:12 +0x48\n"}
|
|
||||||
{"Time":"2026-10-25T15:34:38.066120239+01:00","Action":"output","Package":"example.com/timeout","Test":"TestWaitsForever","Output":"testing.tRunner(0x3a0209bc8488, 0x6d5b10)\n"}
|
|
||||||
{"Time":"2026-10-25T15:34:38.066123509+01:00","Action":"output","Package":"example.com/timeout","Test":"TestWaitsForever","Output":"\t/usr/lib/go/src/testing/testing.go:2195 +0xea\n"}
|
|
||||||
{"Time":"2026-10-25T15:34:38.066126839+01:00","Action":"output","Package":"example.com/timeout","Test":"TestWaitsForever","Output":"created by testing.(*T).Run in goroutine 1\n"}
|
|
||||||
{"Time":"2026-10-25T15:34:38.066129989+01:00","Action":"output","Package":"example.com/timeout","Test":"TestWaitsForever","Output":"\t/usr/lib/go/src/testing/testing.go:2260 +0x4d4\n"}
|
|
||||||
{"Time":"2026-10-25T15:34:38.066986453+01:00","Action":"output","Package":"example.com/timeout","Output":"FAIL\texample.com/timeout\t2.017s\n","OutputType":"frame"}
|
|
||||||
{"Time":"2026-10-25T15:34:38.067002113+01:00","Action":"fail","Package":"example.com/timeout","Elapsed":2.017}
|
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
{"Time":"2026-10-24T23:45:37.721718705+02:00","Action":"start","Package":"fixture/twofailing/one"}
|
||||||
|
{"Time":"2026-10-24T23:45:37.723685152+02:00","Action":"run","Package":"fixture/twofailing/one","Test":"TestOneIsTwo"}
|
||||||
|
{"Time":"2026-10-24T23:45:37.723726472+02:00","Action":"output","Package":"fixture/twofailing/one","Test":"TestOneIsTwo","Output":"=== RUN TestOneIsTwo\n","OutputType":"frame"}
|
||||||
|
{"Time":"2026-10-24T23:45:37.723743682+02:00","Action":"output","Package":"fixture/twofailing/one","Test":"TestOneIsTwo","Output":" one_test.go:6: checking One\n"}
|
||||||
|
{"Time":"2026-10-24T23:45:37.723750622+02:00","Action":"output","Package":"fixture/twofailing/one","Test":"TestOneIsTwo","Output":" one_test.go:8: One() = 1, want 2\n","OutputType":"error"}
|
||||||
|
{"Time":"2026-10-24T23:45:37.723759302+02:00","Action":"output","Package":"fixture/twofailing/one","Test":"TestOneIsTwo","Output":"--- FAIL: TestOneIsTwo (0.00s)\n","OutputType":"frame"}
|
||||||
|
{"Time":"2026-10-24T23:45:37.723889303+02:00","Action":"start","Package":"fixture/twofailing/two"}
|
||||||
|
{"Time":"2026-10-24T23:45:37.723762972+02:00","Action":"fail","Package":"fixture/twofailing/one","Test":"TestOneIsTwo","Elapsed":0}
|
||||||
|
{"Time":"2026-10-24T23:45:37.723952274+02:00","Action":"run","Package":"fixture/twofailing/one","Test":"TestOneIsOne"}
|
||||||
|
{"Time":"2026-10-24T23:45:37.723955484+02:00","Action":"output","Package":"fixture/twofailing/one","Test":"TestOneIsOne","Output":"=== RUN TestOneIsOne\n","OutputType":"frame"}
|
||||||
|
{"Time":"2026-10-24T23:45:37.723959304+02:00","Action":"output","Package":"fixture/twofailing/one","Test":"TestOneIsOne","Output":"--- PASS: TestOneIsOne (0.00s)\n","OutputType":"frame"}
|
||||||
|
{"Time":"2026-10-24T23:45:37.723964914+02:00","Action":"pass","Package":"fixture/twofailing/one","Test":"TestOneIsOne","Elapsed":0}
|
||||||
|
{"Time":"2026-10-24T23:45:37.723967704+02:00","Action":"output","Package":"fixture/twofailing/one","Output":"FAIL\n","OutputType":"frame"}
|
||||||
|
{"Time":"2026-10-24T23:45:37.724225126+02:00","Action":"output","Package":"fixture/twofailing/one","Output":"FAIL\tfixture/twofailing/one\t0.002s\n","OutputType":"frame"}
|
||||||
|
{"Time":"2026-10-24T23:45:37.724231936+02:00","Action":"fail","Package":"fixture/twofailing/one","Elapsed":0.003}
|
||||||
|
{"Time":"2026-10-24T23:45:37.725752949+02:00","Action":"run","Package":"fixture/twofailing/two","Test":"TestTwoIsThree"}
|
||||||
|
{"Time":"2026-10-24T23:45:37.725796309+02:00","Action":"output","Package":"fixture/twofailing/two","Test":"TestTwoIsThree","Output":"=== RUN TestTwoIsThree\n","OutputType":"frame"}
|
||||||
|
{"Time":"2026-10-24T23:45:37.725808239+02:00","Action":"output","Package":"fixture/twofailing/two","Test":"TestTwoIsThree","Output":" two_test.go:7: Two() = 2, want 3\n","OutputType":"error"}
|
||||||
|
{"Time":"2026-10-24T23:45:37.72581671+02:00","Action":"output","Package":"fixture/twofailing/two","Test":"TestTwoIsThree","Output":"--- FAIL: TestTwoIsThree (0.00s)\n","OutputType":"frame"}
|
||||||
|
{"Time":"2026-10-24T23:45:37.72582026+02:00","Action":"fail","Package":"fixture/twofailing/two","Test":"TestTwoIsThree","Elapsed":0}
|
||||||
|
{"Time":"2026-10-24T23:45:37.72582549+02:00","Action":"output","Package":"fixture/twofailing/two","Output":"FAIL\n","OutputType":"frame"}
|
||||||
|
{"Time":"2026-10-24T23:45:37.726319344+02:00","Action":"output","Package":"fixture/twofailing/two","Output":"FAIL\tfixture/twofailing/two\t0.002s\n","OutputType":"frame"}
|
||||||
|
{"Time":"2026-10-24T23:45:37.726362544+02:00","Action":"fail","Package":"fixture/twofailing/two","Elapsed":0.002}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
module fixture/twofailing
|
||||||
|
|
||||||
|
go 1.25.0
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
package one
|
||||||
|
|
||||||
|
func One() int { return 1 }
|
||||||
+16
@@ -0,0 +1,16 @@
|
|||||||
|
package one
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestOneIsTwo(t *testing.T) {
|
||||||
|
t.Log("checking One")
|
||||||
|
if One() != 2 {
|
||||||
|
t.Fatalf("One() = %d, want 2", One())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOneIsOne(t *testing.T) {
|
||||||
|
if One() != 1 {
|
||||||
|
t.Fatal("one")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
package two
|
||||||
|
|
||||||
|
func Two() int { return 2 }
|
||||||
+9
@@ -0,0 +1,9 @@
|
|||||||
|
package two
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestTwoIsThree(t *testing.T) {
|
||||||
|
if Two() != 3 {
|
||||||
|
t.Errorf("Two() = %d, want 3", Two())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,7 +3,7 @@ module meshbuildgo
|
|||||||
go 1.26.0
|
go 1.26.0
|
||||||
|
|
||||||
require (
|
require (
|
||||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261025164403-a8fbabbfc373
|
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261024232922-99407c37df95
|
||||||
github.com/distribution/distribution/v3 v3.1.2
|
github.com/distribution/distribution/v3 v3.1.2
|
||||||
github.com/nats-io/nats-server/v2 v2.11.17
|
github.com/nats-io/nats-server/v2 v2.11.17
|
||||||
github.com/nats-io/nats.go v1.54.0
|
github.com/nats-io/nats.go v1.54.0
|
||||||
|
|||||||
@@ -1,5 +1,13 @@
|
|||||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261025164403-a8fbabbfc373 h1:Bykd8L6nHGNsvpHk3hLMeOct75UwlboHxasqB4cGKvQ=
|
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261024214154-bc16740feb0d h1:oJREw0ggKqJWJ5DEISSL0i2ZC5XqdXAidxo9NJ74TCc=
|
||||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261025164403-a8fbabbfc373/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
|
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261024214154-bc16740feb0d/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
|
||||||
|
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261024225846-cb548a6a588c h1:GWHP9ozfkFXHVEL2gE4Xa5kc5e8uPO+UL9+mau6iaIs=
|
||||||
|
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261024225846-cb548a6a588c/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
|
||||||
|
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261024230839-4a12ccb78d19 h1:NqU4hkk/jHdTEcGFnnXvdOJwD8dH5u+qWaCdQQ+JYMc=
|
||||||
|
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261024230839-4a12ccb78d19/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
|
||||||
|
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261024232250-9b408db24a58 h1:4Damtdqlf6u6nJhvMA99peDy1jmjixtcwFXJ4bWEkLk=
|
||||||
|
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261024232250-9b408db24a58/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
|
||||||
|
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261024232922-99407c37df95 h1:I8zbgGzRjmgYydPTWo8hmaZW/P/g+qGDUfu3E32/pkU=
|
||||||
|
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261024232922-99407c37df95/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
|
||||||
github.com/alicebob/miniredis/v2 v2.35.0 h1:QwLphYqCEAo1eu1TqPRN2jgVMPBweeQcR21jeqDCONI=
|
github.com/alicebob/miniredis/v2 v2.35.0 h1:QwLphYqCEAo1eu1TqPRN2jgVMPBweeQcR21jeqDCONI=
|
||||||
github.com/alicebob/miniredis/v2 v2.35.0/go.mod h1:TcL7YfarKPGDAthEtl5NBeHZfeUQj6OXMm/+iu5cLMM=
|
github.com/alicebob/miniredis/v2 v2.35.0/go.mod h1:TcL7YfarKPGDAthEtl5NBeHZfeUQj6OXMm/+iu5cLMM=
|
||||||
github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op h1:Z/MZK75wC/NSrkgqeNIa7jexam9uWzhLmFTSCPI/kn0=
|
github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op h1:Z/MZK75wC/NSrkgqeNIa7jexam9uWzhLmFTSCPI/kn0=
|
||||||
|
|||||||
@@ -37,12 +37,8 @@ func (m *Module) Puller(steps []StepTool, source buildstep.AskSource) *buildstep
|
|||||||
source = buildstep.SeatAsks{Language: delivery.AskImage}
|
source = buildstep.SeatAsks{Language: delivery.AskImage}
|
||||||
}
|
}
|
||||||
capacity := m.Capacity
|
capacity := m.Capacity
|
||||||
// The capacity's own change wakes the puller (issue 1887): the runtime's watch of the config value as mesh-config
|
|
||||||
// resolved it; the settings file is read only when that watch cannot start. A test's Capacity is its own.
|
|
||||||
var watch func(string, func(stdio.ProvisionChange) error) error
|
|
||||||
if capacity == nil {
|
if capacity == nil {
|
||||||
capacity = buildstep.NodeCapacity(m.Settings, buildstep.DefaultCapacities, m.Node, m.Say)
|
capacity = buildstep.NodeCapacity(m.Settings, buildstep.DefaultCapacities, m.Node, m.Say)
|
||||||
watch = buildstep.WatchConfigServed
|
|
||||||
}
|
}
|
||||||
ask := m.askSteps(steps)
|
ask := m.askSteps(steps)
|
||||||
var load func(string) ([]byte, error)
|
var load func(string) ([]byte, error)
|
||||||
@@ -50,7 +46,7 @@ func (m *Module) Puller(steps []StepTool, source buildstep.AskSource) *buildstep
|
|||||||
load = func(key string) ([]byte, error) { return m.ReadSteps.Get(key) }
|
load = func(key string) ([]byte, error) { return m.ReadSteps.Get(key) }
|
||||||
}
|
}
|
||||||
return &buildstep.Puller{Source: source, Language: delivery.AskImage, Node: m.Node, Put: m.Steps, Load: load,
|
return &buildstep.Puller{Source: source, Language: delivery.AskImage, Node: m.Node, Put: m.Steps, Load: load,
|
||||||
Capacity: capacity, WatchConfig: watch, Logf: m.Say,
|
Capacity: capacity, Logf: m.Say,
|
||||||
Run: func(ctx context.Context, a delivery.StepAsk) (any, error) {
|
Run: func(ctx context.Context, a delivery.StepAsk) (any, error) {
|
||||||
if err := m.Check(ctx); err != nil {
|
if err := m.Check(ctx); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ module meshbuildimage
|
|||||||
go 1.26.0
|
go 1.26.0
|
||||||
|
|
||||||
require (
|
require (
|
||||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261025164403-a8fbabbfc373
|
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261023213200-403a5d72ab93
|
||||||
github.com/nats-io/nats-server/v2 v2.11.17
|
github.com/nats-io/nats-server/v2 v2.11.17
|
||||||
github.com/nats-io/nats.go v1.54.0
|
github.com/nats-io/nats.go v1.54.0
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261025164403-a8fbabbfc373 h1:Bykd8L6nHGNsvpHk3hLMeOct75UwlboHxasqB4cGKvQ=
|
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261023213200-403a5d72ab93 h1:FAiFY2KBKEoZSH49OZTt7/zRBIQxyl5g/KhYrpggbN4=
|
||||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261025164403-a8fbabbfc373/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
|
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261023213200-403a5d72ab93/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
|
||||||
github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op h1:Z/MZK75wC/NSrkgqeNIa7jexam9uWzhLmFTSCPI/kn0=
|
github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op h1:Z/MZK75wC/NSrkgqeNIa7jexam9uWzhLmFTSCPI/kn0=
|
||||||
github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op/go.mod h1:FQyySiasQQM8735Ddel3MRojmy4dA1IqCeyJ5jmPMbI=
|
github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op/go.mod h1:FQyySiasQQM8735Ddel3MRojmy4dA1IqCeyJ5jmPMbI=
|
||||||
github.com/google/go-tpm v0.9.8 h1:slArAR9Ft+1ybZu0lBwpSmpwhRXaa85hWtMinMyRAWo=
|
github.com/google/go-tpm v0.9.8 h1:slArAR9Ft+1ybZu0lBwpSmpwhRXaa85hWtMinMyRAWo=
|
||||||
|
|||||||
@@ -45,12 +45,8 @@ func (m *Module) Puller(steps []StepTool, source buildstep.AskSource) *buildstep
|
|||||||
source = buildstep.SeatAsks{Language: delivery.AskNode}
|
source = buildstep.SeatAsks{Language: delivery.AskNode}
|
||||||
}
|
}
|
||||||
capacity := m.Capacity
|
capacity := m.Capacity
|
||||||
// The capacity's own change wakes the puller (issue 1887): the runtime's watch of the config value as mesh-config
|
|
||||||
// resolved it; the settings file is read only when that watch cannot start. A test's Capacity is its own.
|
|
||||||
var watch func(string, func(stdio.ProvisionChange) error) error
|
|
||||||
if capacity == nil {
|
if capacity == nil {
|
||||||
capacity = buildstep.NodeCapacity(m.Settings, buildstep.DefaultCapacities, m.Node, m.Say)
|
capacity = buildstep.NodeCapacity(m.Settings, buildstep.DefaultCapacities, m.Node, m.Say)
|
||||||
watch = buildstep.WatchConfigServed
|
|
||||||
}
|
}
|
||||||
ask := m.askSteps(steps)
|
ask := m.askSteps(steps)
|
||||||
var load func(string) ([]byte, error)
|
var load func(string) ([]byte, error)
|
||||||
@@ -58,7 +54,7 @@ func (m *Module) Puller(steps []StepTool, source buildstep.AskSource) *buildstep
|
|||||||
load = func(key string) ([]byte, error) { return m.ReadSteps.Get(key) }
|
load = func(key string) ([]byte, error) { return m.ReadSteps.Get(key) }
|
||||||
}
|
}
|
||||||
return &buildstep.Puller{Source: source, Language: delivery.AskNode, Node: m.Node, Put: m.Steps, Load: load,
|
return &buildstep.Puller{Source: source, Language: delivery.AskNode, Node: m.Node, Put: m.Steps, Load: load,
|
||||||
Capacity: capacity, WatchConfig: watch, Logf: m.Say,
|
Capacity: capacity, Logf: m.Say,
|
||||||
Run: func(ctx context.Context, a delivery.StepAsk) (any, error) {
|
Run: func(ctx context.Context, a delivery.StepAsk) (any, error) {
|
||||||
if err := m.Prepare(ctx); err != nil {
|
if err := m.Prepare(ctx); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ module meshbuildnode
|
|||||||
go 1.26.0
|
go 1.26.0
|
||||||
|
|
||||||
require (
|
require (
|
||||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261025164403-a8fbabbfc373
|
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261024232922-99407c37df95
|
||||||
github.com/distribution/distribution/v3 v3.1.2
|
github.com/distribution/distribution/v3 v3.1.2
|
||||||
github.com/nats-io/nats.go v1.54.0
|
github.com/nats-io/nats.go v1.54.0
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261025164403-a8fbabbfc373 h1:Bykd8L6nHGNsvpHk3hLMeOct75UwlboHxasqB4cGKvQ=
|
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261024214154-bc16740feb0d h1:oJREw0ggKqJWJ5DEISSL0i2ZC5XqdXAidxo9NJ74TCc=
|
||||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261025164403-a8fbabbfc373/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
|
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261024214154-bc16740feb0d/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
|
||||||
|
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261024232922-99407c37df95 h1:I8zbgGzRjmgYydPTWo8hmaZW/P/g+qGDUfu3E32/pkU=
|
||||||
|
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261024232922-99407c37df95/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
|
||||||
github.com/alicebob/miniredis/v2 v2.35.0 h1:QwLphYqCEAo1eu1TqPRN2jgVMPBweeQcR21jeqDCONI=
|
github.com/alicebob/miniredis/v2 v2.35.0 h1:QwLphYqCEAo1eu1TqPRN2jgVMPBweeQcR21jeqDCONI=
|
||||||
github.com/alicebob/miniredis/v2 v2.35.0/go.mod h1:TcL7YfarKPGDAthEtl5NBeHZfeUQj6OXMm/+iu5cLMM=
|
github.com/alicebob/miniredis/v2 v2.35.0/go.mod h1:TcL7YfarKPGDAthEtl5NBeHZfeUQj6OXMm/+iu5cLMM=
|
||||||
github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op h1:Z/MZK75wC/NSrkgqeNIa7jexam9uWzhLmFTSCPI/kn0=
|
github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op h1:Z/MZK75wC/NSrkgqeNIa7jexam9uWzhLmFTSCPI/kn0=
|
||||||
|
|||||||
Binary file not shown.
+15
-179
@@ -61,74 +61,10 @@ const ONLYOFFICE_KEYS: ReadonlyArray<readonly [occKey: string, configKey: string
|
|||||||
|
|
||||||
let saidOnlyOfficeNotGiven = false;
|
let saidOnlyOfficeNotGiven = false;
|
||||||
|
|
||||||
/**
|
|
||||||
* The one Nextcloud account the module makes and keeps for itself: the same name files/server.env
|
|
||||||
* gives NEXTCLOUD_ADMIN_USER (novox/hq issue 1253). Only this account is ever repaired; a `user`
|
|
||||||
* setting naming any other account is used as given and never written to.
|
|
||||||
*/
|
|
||||||
export const MESH_ACCOUNT = "mesh-admin";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Nextcloud refused the mesh's own login on its OCS surface (HTTP 401), or throttled it after
|
|
||||||
* refusals (HTTP 429, its brute-force protection). Said by its status and the account, never a
|
|
||||||
* password; the events loop backs off on it instead of asking again every tick.
|
|
||||||
*/
|
|
||||||
export class LoginRefusedError extends Error {
|
|
||||||
constructor(readonly status: number, readonly user: string, detail: string) {
|
|
||||||
super(`Nextcloud refused the login of ${user} (${status}${detail ? ": " + detail : ""})`);
|
|
||||||
this.name = "LoginRefusedError";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** The OCS envelope's own message, the one line worth saying of a refusal; empty when none. */
|
|
||||||
function ocsMessage(body: string): string {
|
|
||||||
const xml = /<message>([^<]*)<\/message>/.exec(body);
|
|
||||||
if (xml) return xml[1].trim();
|
|
||||||
try { return String(JSON.parse(body)?.ocs?.meta?.message ?? "").trim(); } catch { return ""; }
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* How long to leave a failing poll alone: the base interval after the first failure, doubling on
|
|
||||||
* each further one up to `maxMs`, and back to every tick once a poll passes. `due` says whether a
|
|
||||||
* tick should poll; `failed` records a failure and answers whether it is the first of a run (said
|
|
||||||
* loud) or the one that reached the ceiling (said once more), so a failure is never said every tick.
|
|
||||||
*/
|
|
||||||
export class Backoff {
|
|
||||||
private delayMs = 0;
|
|
||||||
private nextAt = 0;
|
|
||||||
private failures = 0;
|
|
||||||
|
|
||||||
constructor(private readonly baseMs: number, private readonly maxMs: number) {}
|
|
||||||
|
|
||||||
due(now: number): boolean {
|
|
||||||
return now >= this.nextAt;
|
|
||||||
}
|
|
||||||
|
|
||||||
failed(now: number): { first: boolean; atCeiling: boolean; delayMs: number } {
|
|
||||||
this.failures += 1;
|
|
||||||
const before = this.delayMs;
|
|
||||||
this.delayMs = before === 0 ? this.baseMs : Math.min(before * 2, this.maxMs);
|
|
||||||
this.nextAt = now + this.delayMs;
|
|
||||||
return { first: this.failures === 1, atCeiling: this.delayMs === this.maxMs && before !== this.maxMs, delayMs: this.delayMs };
|
|
||||||
}
|
|
||||||
|
|
||||||
/** A poll passed; answers whether it ended a run of failures. */
|
|
||||||
passed(): boolean {
|
|
||||||
const wasFailing = this.failures > 0;
|
|
||||||
this.failures = 0;
|
|
||||||
this.delayMs = 0;
|
|
||||||
this.nextAt = 0;
|
|
||||||
return wasFailing;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class NextcloudClient {
|
export class NextcloudClient {
|
||||||
// Whether the last connection check failed (novox/hq issue 1832): while it did, every tick runs
|
// Whether the last connection check failed (novox/hq issue 1832): while it did, every tick runs
|
||||||
// the check again, even when nothing changed, until it passes.
|
// the check again, even when nothing changed, until it passes.
|
||||||
private lastCheckFailed = false;
|
private lastCheckFailed = false;
|
||||||
// Whether the account was already made right since the last accepted login (novox/hq issue
|
|
||||||
// 1253): a refusal makes it right once, and a refusal after that is said, not repaired again.
|
|
||||||
private accountMadeRight = false;
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly container: string,
|
private readonly container: string,
|
||||||
@@ -140,17 +76,15 @@ export class NextcloudClient {
|
|||||||
/**
|
/**
|
||||||
* Build from the module's resolved environment. occ needs only the container name (default
|
* Build from the module's resolved environment. occ needs only the container name (default
|
||||||
* "nextcloud"); the OCS surface needs the admin password the module keeps as its own secret
|
* "nextcloud"); the OCS surface needs the admin password the module keeps as its own secret
|
||||||
* (MESH_NEXTCLOUD_ADMIN_PASSWORD, user MESH_NEXTCLOUD_ADMIN_USER default mesh-admin, URL the local
|
* (MESH_NEXTCLOUD_ADMIN_PASSWORD, user MESH_NEXTCLOUD_ADMIN_USER default admin, URL the local
|
||||||
* container). The admin password is treated as the "configured for mesh administration" signal:
|
* container). The admin password is treated as the "configured for mesh administration" signal:
|
||||||
* throws without it, and the module then contributes nothing rather than failing.
|
* throws without it, and the module then contributes nothing rather than failing.
|
||||||
*/
|
*/
|
||||||
static fromEnv(env: NodeJS.ProcessEnv = process.env): NextcloudClient {
|
static fromEnv(env: NodeJS.ProcessEnv = process.env): NextcloudClient {
|
||||||
const cfg = meshConfig(env.MESH_NEXTCLOUD_CONFIG_FILE);
|
const cfg = meshConfig(env.MESH_NEXTCLOUD_CONFIG_FILE);
|
||||||
// An empty setting is the manifest's "empty is the default", never an empty name.
|
const container = cfg.container ?? env.MESH_NEXTCLOUD_CONTAINER ?? "nextcloud";
|
||||||
const container = cfg.container || env.MESH_NEXTCLOUD_CONTAINER || "nextcloud";
|
const ocsUrl = cfg.url ?? env.MESH_NEXTCLOUD_URL ?? `http://127.0.0.1:${env.NEXTCLOUD_PORT ?? "80"}`;
|
||||||
const ocsUrl = cfg.url || env.MESH_NEXTCLOUD_URL || `http://127.0.0.1:${env.NEXTCLOUD_PORT ?? "80"}`;
|
const adminUser = cfg.user ?? env.MESH_NEXTCLOUD_ADMIN_USER ?? "admin";
|
||||||
// No "admin" fallback: that is what a hand-made install calls its human administrator.
|
|
||||||
const adminUser = cfg.user || env.MESH_NEXTCLOUD_ADMIN_USER || MESH_ACCOUNT;
|
|
||||||
const passwordFile = env.MESH_NEXTCLOUD_ADMIN_PASSWORD_FILE;
|
const passwordFile = env.MESH_NEXTCLOUD_ADMIN_PASSWORD_FILE;
|
||||||
ignoreSecretSetting(cfg, "password");
|
ignoreSecretSetting(cfg, "password");
|
||||||
const adminPassword = env.MESH_NEXTCLOUD_ADMIN_PASSWORD
|
const adminPassword = env.MESH_NEXTCLOUD_ADMIN_PASSWORD
|
||||||
@@ -166,28 +100,20 @@ export class NextcloudClient {
|
|||||||
* Run occ inside the container as the web user, returning its stdout, throwing its own message.
|
* Run occ inside the container as the web user, returning its stdout, throwing its own message.
|
||||||
* With `opts.input` given, occ reads it from stdin (`docker exec -i`) instead of taking it on the
|
* With `opts.input` given, occ reads it from stdin (`docker exec -i`) instead of taking it on the
|
||||||
* command line — the only way a secret value reaches occ without sitting in the docker CLI's argv,
|
* command line — the only way a secret value reaches occ without sitting in the docker CLI's argv,
|
||||||
* the container's php argv and docker's exec record (novox/hq issue 1831). With `opts.env` given,
|
* the container's php argv and docker's exec record (novox/hq issue 1831).
|
||||||
* each variable is passed by name alone (`docker exec -e NAME`), its value taken from the docker
|
|
||||||
* CLI's own environment, so it is on no argv either (occ's `--password-from-env` reads OC_PASS).
|
|
||||||
*/
|
*/
|
||||||
occ(args: string[], opts: { input?: string; env?: Record<string, string> } = {}): string {
|
occ(args: string[], opts: { input?: string } = {}): string {
|
||||||
const dockerArgs = ["exec"];
|
const dockerArgs = opts.input !== undefined
|
||||||
if (opts.input !== undefined) dockerArgs.push("-i");
|
? ["exec", "-i", "-u", "www-data", this.container, "php", "occ", ...args]
|
||||||
for (const name of Object.keys(opts.env ?? {})) dockerArgs.push("-e", name);
|
: ["exec", "-u", "www-data", this.container, "php", "occ", ...args];
|
||||||
dockerArgs.push("-u", "www-data", this.container, "php", "occ", ...args);
|
|
||||||
try {
|
try {
|
||||||
return execFileSync("docker", dockerArgs, {
|
return execFileSync("docker", dockerArgs, {
|
||||||
encoding: "utf8", timeout: 60_000,
|
encoding: "utf8", timeout: 60_000,
|
||||||
...(opts.input !== undefined ? { input: opts.input } : {}),
|
...(opts.input !== undefined ? { input: opts.input } : {}),
|
||||||
...(opts.env ? { env: { ...process.env, ...opts.env } } : {}),
|
|
||||||
}).trim();
|
}).trim();
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
// occ writes many refusals ("user not found") to stdout with an empty stderr: say both.
|
const detail = String(err?.stderr ?? err?.stdout ?? err?.message ?? "").trim();
|
||||||
const detail = [err?.stderr, err?.stdout].map((x) => String(x ?? "").trim()).filter(Boolean).join("\n")
|
throw new Error(detail || `occ produced no output — is the ${this.container} container running?`);
|
||||||
|| String(err?.message ?? "").trim();
|
|
||||||
// The exit status rides along, so a caller can tell occ's own answer from a failure.
|
|
||||||
throw Object.assign(new Error(detail || `occ produced no output — is the ${this.container} container running?`),
|
|
||||||
{ status: typeof err?.status === "number" ? err.status : undefined });
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -262,75 +188,14 @@ export class NextcloudClient {
|
|||||||
return { enabled: Object.keys(parsed.enabled ?? {}), disabled: Object.keys(parsed.disabled ?? {}) };
|
return { enabled: Object.keys(parsed.enabled ?? {}), disabled: Object.keys(parsed.disabled ?? {}) };
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** List every share, over the OCS Sharing API with the admin credentials. */
|
||||||
* Makes the mesh's own account in Nextcloud match the module's own secret (novox/hq issue 1253).
|
async listShares(): Promise<NextcloudShare[]> {
|
||||||
* The image creates NEXTCLOUD_ADMIN_USER only when it installs a fresh instance, so an instance
|
|
||||||
* the mesh adopted never has it, and a secret made anew never reaches it. Only the module's own
|
|
||||||
* account (MESH_ACCOUNT) is touched: under any other name it writes nothing and throws
|
|
||||||
* LoginRefusedError. Through occ: whether the account exists is read from `user:info`, whose
|
|
||||||
* exit status 1 with "not found" means missing (user:list pages, so it is not asked); a missing
|
|
||||||
* one is added, an existing one gets its password set to the secret. It gets no admin group: the
|
|
||||||
* shares poll needs no admin rights (least privilege). The password goes by OC_PASS
|
|
||||||
* (`--password-from-env`), never on an argv. Nextcloud's brute-force record is left alone: it is
|
|
||||||
* shared by every client behind the same address, and the caller's back-off outlasts it. A
|
|
||||||
* failing occ is thrown as LoginRefusedError carrying occ's own message.
|
|
||||||
*/
|
|
||||||
ensureAdminAccount(): "created" | "password-set" {
|
|
||||||
if (this.adminUser !== MESH_ACCOUNT) {
|
|
||||||
throw new LoginRefusedError(401, this.adminUser, `not repaired: only ${MESH_ACCOUNT}, the module's own account, is`);
|
|
||||||
}
|
|
||||||
const pass = { env: { OC_PASS: this.adminPassword } };
|
|
||||||
try {
|
|
||||||
if (!this.accountExists()) {
|
|
||||||
this.occ(["user:add", "--password-from-env", "--display-name=Mesh administration", this.adminUser], pass);
|
|
||||||
return "created";
|
|
||||||
}
|
|
||||||
this.occ(["user:resetpassword", "--password-from-env", this.adminUser], pass);
|
|
||||||
return "password-set";
|
|
||||||
} catch (err) {
|
|
||||||
throw new LoginRefusedError(401, this.adminUser, `repair failed: ${String((err as Error)?.message ?? err).split("\n")[0]}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Whether the account exists: occ user:info answers it, exit 1 and "not found" when missing. */
|
|
||||||
private accountExists(): boolean {
|
|
||||||
try {
|
|
||||||
this.occ(["user:info", this.adminUser, "--output=json"]);
|
|
||||||
return true;
|
|
||||||
} catch (err: any) {
|
|
||||||
if (err?.status === 1 && /not found/i.test(String(err?.message ?? ""))) return false;
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fetchShares(): Promise<Response> {
|
|
||||||
const auth = Buffer.from(`${this.adminUser}:${this.adminPassword}`).toString("base64");
|
const auth = Buffer.from(`${this.adminUser}:${this.adminPassword}`).toString("base64");
|
||||||
return fetch(
|
const res = await fetch(
|
||||||
`${this.ocsUrl}/ocs/v2.php/apps/files_sharing/api/v1/shares?format=json`,
|
`${this.ocsUrl}/ocs/v2.php/apps/files_sharing/api/v1/shares?format=json`,
|
||||||
{ headers: { Authorization: `Basic ${auth}`, "OCS-APIRequest": "true", Accept: "application/json" } },
|
{ headers: { Authorization: `Basic ${auth}`, "OCS-APIRequest": "true", Accept: "application/json" } },
|
||||||
);
|
);
|
||||||
}
|
if (!res.ok) throw new Error(`Nextcloud OCS shares: ${res.status} ${await res.text()}`);
|
||||||
|
|
||||||
/**
|
|
||||||
* List every share, over the OCS Sharing API as the mesh's own account. A refused login (401)
|
|
||||||
* makes the account right once (ensureAdminAccount) and asks again; a refusal after that, or
|
|
||||||
* any throttle (429), throws LoginRefusedError, so the caller backs off (novox/hq issue 1253).
|
|
||||||
*/
|
|
||||||
async listShares(): Promise<NextcloudShare[]> {
|
|
||||||
let res = await this.fetchShares();
|
|
||||||
// Only a refused login (401) is repaired. A throttle (429) is no evidence the password is
|
|
||||||
// wrong (anyone guessing passwords from the same address causes it): it is backed off only.
|
|
||||||
if (res.status === 401 && !this.accountMadeRight) {
|
|
||||||
await res.text();
|
|
||||||
this.accountMadeRight = true;
|
|
||||||
this.ensureAdminAccount();
|
|
||||||
res = await this.fetchShares();
|
|
||||||
}
|
|
||||||
if (res.status === 401 || res.status === 429) {
|
|
||||||
throw new LoginRefusedError(res.status, this.adminUser, ocsMessage(await res.text()));
|
|
||||||
}
|
|
||||||
if (!res.ok) throw new Error(`Nextcloud OCS shares: ${res.status} ${ocsMessage(await res.text())}`.trim());
|
|
||||||
this.accountMadeRight = false;
|
|
||||||
const rows = ((await res.json())?.ocs?.data ?? []) as any[];
|
const rows = ((await res.json())?.ocs?.data ?? []) as any[];
|
||||||
return rows.map((s) => ({
|
return rows.map((s) => ({
|
||||||
id: String(s.id),
|
id: String(s.id),
|
||||||
@@ -341,32 +206,3 @@ export class NextcloudClient {
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Runs one poll of the shares under a back-off (novox/hq issue 1253): skipped while backing off;
|
|
||||||
* a LoginRefusedError is said loud at the first failure and once more when the wait reaches its
|
|
||||||
* ceiling, never on every tick; one line says when it passes again. Other errors are thrown on,
|
|
||||||
* so the loop logs them as before.
|
|
||||||
*/
|
|
||||||
export async function pollSharesGuarded(
|
|
||||||
client: NextcloudClient,
|
|
||||||
backoff: Backoff,
|
|
||||||
poll: (c: NextcloudClient) => Promise<void>,
|
|
||||||
now: () => number = Date.now,
|
|
||||||
): Promise<void> {
|
|
||||||
if (!backoff.due(now())) return;
|
|
||||||
try {
|
|
||||||
await poll(client);
|
|
||||||
} catch (err) {
|
|
||||||
if (!(err instanceof LoginRefusedError)) throw err;
|
|
||||||
const step = backoff.failed(now());
|
|
||||||
const minutes = Math.round(step.delayMs / 60_000);
|
|
||||||
if (step.first) {
|
|
||||||
console.error(`[nextcloud] shares: ${err.message}; not asking again for ${minutes} min, then less often, up to every hour`);
|
|
||||||
} else if (step.atCeiling) {
|
|
||||||
console.error(`[nextcloud] shares: ${err.message}, still; asking once an hour from now on`);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (backoff.passed()) console.log("[nextcloud] shares: the login is accepted again");
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
// existing user and share as freshly created.
|
// existing user and share as freshly created.
|
||||||
|
|
||||||
import { emit } from "@novox/mesh-sdk/events";
|
import { emit } from "@novox/mesh-sdk/events";
|
||||||
import { Backoff, NextcloudClient, pollSharesGuarded } from "./client.js";
|
import { NextcloudClient } from "./client.js";
|
||||||
|
|
||||||
// Constructed lazily so an unconfigured node (no admin password) loads this entrypoint without
|
// Constructed lazily so an unconfigured node (no admin password) loads this entrypoint without
|
||||||
// crashing the events host — it simply watches nothing.
|
// crashing the events host — it simply watches nothing.
|
||||||
@@ -69,11 +69,7 @@ if (nextcloud) {
|
|||||||
run();
|
run();
|
||||||
};
|
};
|
||||||
tick(pollUsers);
|
tick(pollUsers);
|
||||||
// A refused login is said once and the shares are left alone for 5 minutes, doubling to an hour,
|
tick(pollShares);
|
||||||
// instead of a refusal every minute that Nextcloud's brute-force protection then throttles
|
|
||||||
// (novox/hq issue 1253).
|
|
||||||
const sharesBackoff = new Backoff(5 * 60_000, 60 * 60_000);
|
|
||||||
tick((c) => pollSharesGuarded(c, sharesBackoff, pollShares));
|
|
||||||
tick(applyOnlyOffice);
|
tick(applyOnlyOffice);
|
||||||
console.log("[nextcloud] watching users, shares and onlyoffice settings");
|
console.log("[nextcloud] watching users, shares and onlyoffice settings");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -72,7 +72,7 @@
|
|||||||
},
|
},
|
||||||
"user": {
|
"user": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "The Nextcloud account the module's tools act as; empty is mesh-admin, the account the module makes and keeps for itself. Any other account is used as given and never repaired.",
|
"description": "The Nextcloud account the module's tools act as; empty is admin.",
|
||||||
"trusted": true,
|
"trusted": true,
|
||||||
"default": ""
|
"default": ""
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,305 +0,0 @@
|
|||||||
// novox/hq issue 1253: the module's own Nextcloud account is made to match its own secret when
|
|
||||||
// Nextcloud refuses the login, and a refusal that stays is said once and backed off, never asked
|
|
||||||
// again every minute into Nextcloud's brute-force throttle. Run against the compiled module, the
|
|
||||||
// way the runtime loads it.
|
|
||||||
|
|
||||||
import { test, mock } from "node:test";
|
|
||||||
import assert from "node:assert/strict";
|
|
||||||
import { chmodSync, mkdtempSync, readFileSync, writeFileSync } from "node:fs";
|
|
||||||
import { tmpdir } from "node:os";
|
|
||||||
import { join } from "node:path";
|
|
||||||
|
|
||||||
import { Backoff, LoginRefusedError, NextcloudClient, pollSharesGuarded } from "../dist/client.js";
|
|
||||||
|
|
||||||
const PASSWORD = "the-modules-own-secret";
|
|
||||||
const REFUSED_XML = `<?xml version="1.0"?>\n<ocs>\n <meta>\n <status>failure</status>\n <statuscode>997</statuscode>\n <message>Unauthorised</message>\n </meta>\n <data/>\n</ocs>\n`;
|
|
||||||
const SHARES = { ocs: { data: [{ id: 7, path: "/Photos", share_type: 3, share_with: "", uid_owner: "jochens" }] } };
|
|
||||||
|
|
||||||
function client(user = "mesh-admin"): any {
|
|
||||||
return new (NextcloudClient as any)("nextcloud", "http://127.0.0.1:20003", user, PASSWORD);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** A stand-in Nextcloud: answers `statuses` in turn (the last one repeating) and counts the asks. */
|
|
||||||
function standInFetch(statuses: number[]) {
|
|
||||||
let asked = 0;
|
|
||||||
const impl = async (): Promise<Response> => {
|
|
||||||
const status = statuses[Math.min(asked, statuses.length - 1)];
|
|
||||||
asked += 1;
|
|
||||||
if (status === 200) return new Response(JSON.stringify(SHARES), { status });
|
|
||||||
return new Response(REFUSED_XML, { status });
|
|
||||||
};
|
|
||||||
return { impl, asked: () => asked };
|
|
||||||
}
|
|
||||||
|
|
||||||
/** A stand-in occ: knows whether the account exists, records every call and every env passed. */
|
|
||||||
function standInOcc(accountExists: boolean, opts: { failWrite?: string } = {}) {
|
|
||||||
const calls: string[][] = [];
|
|
||||||
const envs: Array<Record<string, string> | undefined> = [];
|
|
||||||
const impl = (args: string[], occOpts: { env?: Record<string, string> } = {}): string => {
|
|
||||||
calls.push(args);
|
|
||||||
envs.push(occOpts.env);
|
|
||||||
if (args[0] === "user:info") {
|
|
||||||
// As the real occ() throws it: occ's stdout as the message, its exit status alongside.
|
|
||||||
if (!accountExists) throw Object.assign(new Error("user not found"), { status: 1 });
|
|
||||||
return "{}";
|
|
||||||
}
|
|
||||||
if (["user:add", "user:resetpassword"].includes(args[0])) {
|
|
||||||
if (opts.failWrite) throw new Error(opts.failWrite);
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
throw new Error("unexpected occ call: " + args.join(" "));
|
|
||||||
};
|
|
||||||
return { calls, envs, impl };
|
|
||||||
}
|
|
||||||
|
|
||||||
/** The occ calls that write: anything but a read of the account. */
|
|
||||||
const writes = (calls: string[][]) => calls.filter((a) => a[0] !== "user:info");
|
|
||||||
|
|
||||||
// 1253/AC1: given an adopted instance without the mesh's account, when Nextcloud refuses the
|
|
||||||
// login, then the account is added (no admin group) with the module's own secret, by OC_PASS and
|
|
||||||
// never on an argv, and the shares are listed; Nextcloud's brute-force record is left alone.
|
|
||||||
test("a refused login adds the missing account, password by environment, and lists the shares", async () => {
|
|
||||||
const c = client();
|
|
||||||
const occ = standInOcc(false);
|
|
||||||
mock.method(c, "occ", occ.impl);
|
|
||||||
const web = standInFetch([401, 200]);
|
|
||||||
const f = mock.method(globalThis, "fetch", web.impl);
|
|
||||||
try {
|
|
||||||
const shares = await c.listShares();
|
|
||||||
assert.equal(web.asked(), 2);
|
|
||||||
assert.deepEqual(shares.map((s: any) => s.id), ["7"]);
|
|
||||||
const add = occ.calls.find((a) => a[0] === "user:add");
|
|
||||||
assert.ok(add, "user:add ran");
|
|
||||||
assert.ok(add.includes("--password-from-env") && add.includes("mesh-admin"));
|
|
||||||
assert.ok(!add.some((a) => a.includes("group")), "no admin group: least privilege");
|
|
||||||
assert.deepEqual(occ.calls.map((a) => a[0]), ["user:info", "user:add"]);
|
|
||||||
assert.equal(occ.envs[occ.calls.indexOf(add)]?.OC_PASS, PASSWORD);
|
|
||||||
assert.ok(!occ.calls.flat().some((a) => a.includes(PASSWORD)), "the password is on no argv");
|
|
||||||
} finally {
|
|
||||||
f.mock.restore();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 1253/AC2: given the account exists with another password, when Nextcloud refuses the login,
|
|
||||||
// then its password is set to the module's own secret, in no group.
|
|
||||||
test("a refused login resets an existing account's password, in no group", async () => {
|
|
||||||
const c = client();
|
|
||||||
const occ = standInOcc(true);
|
|
||||||
mock.method(c, "occ", occ.impl);
|
|
||||||
const f = mock.method(globalThis, "fetch", standInFetch([401, 200]).impl);
|
|
||||||
try {
|
|
||||||
await c.listShares();
|
|
||||||
assert.deepEqual(occ.calls.map((a) => a[0]), ["user:info", "user:resetpassword"]);
|
|
||||||
assert.equal(occ.envs[1]?.OC_PASS, PASSWORD);
|
|
||||||
assert.ok(!occ.calls.flat().some((a) => a.includes(PASSWORD)));
|
|
||||||
} finally {
|
|
||||||
f.mock.restore();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 1253/AC3: given the login is still refused after the account was made right, then
|
|
||||||
// listShares throws LoginRefusedError naming the status and the account, never the password or
|
|
||||||
// the whole XML, and a later call does not repair the account again.
|
|
||||||
test("a refusal after the repair throws LoginRefusedError once-repaired, short and without the password", async () => {
|
|
||||||
const c = client();
|
|
||||||
const occ = standInOcc(true);
|
|
||||||
mock.method(c, "occ", occ.impl);
|
|
||||||
const web = standInFetch([401]);
|
|
||||||
const f = mock.method(globalThis, "fetch", web.impl);
|
|
||||||
try {
|
|
||||||
await assert.rejects(c.listShares(), (err: any) => {
|
|
||||||
assert.ok(err instanceof LoginRefusedError);
|
|
||||||
assert.equal(err.status, 401);
|
|
||||||
assert.equal(err.message, "Nextcloud refused the login of mesh-admin (401: Unauthorised)");
|
|
||||||
assert.ok(!err.message.includes(PASSWORD));
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
await assert.rejects(c.listShares(), LoginRefusedError);
|
|
||||||
assert.equal(occ.calls.filter((a) => a[0] === "user:resetpassword").length, 1, "repaired once");
|
|
||||||
assert.equal(web.asked(), 3);
|
|
||||||
} finally {
|
|
||||||
f.mock.restore();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 1253/AC3b: a throttle (429) is no evidence the password is wrong: two cycles of 429 then 200
|
|
||||||
// run no occ write, and the 429s are LoginRefusedError for the back-off.
|
|
||||||
test("a throttle (429) is backed off, never repaired", async () => {
|
|
||||||
const c = client();
|
|
||||||
const occ = standInOcc(true);
|
|
||||||
mock.method(c, "occ", occ.impl);
|
|
||||||
const f = mock.method(globalThis, "fetch", standInFetch([429, 200, 429, 200]).impl);
|
|
||||||
try {
|
|
||||||
for (let cycle = 0; cycle < 2; cycle++) {
|
|
||||||
await assert.rejects(c.listShares(), (err: any) => err instanceof LoginRefusedError && err.status === 429);
|
|
||||||
assert.equal((await c.listShares()).length, 1);
|
|
||||||
}
|
|
||||||
assert.deepEqual(occ.calls, [], "no occ call at all, so no write");
|
|
||||||
} finally {
|
|
||||||
f.mock.restore();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 1253/AC3c: a 401 runs exactly one repair.
|
|
||||||
test("a 401 runs exactly one repair", async () => {
|
|
||||||
const c = client();
|
|
||||||
const occ = standInOcc(true);
|
|
||||||
mock.method(c, "occ", occ.impl);
|
|
||||||
const f = mock.method(globalThis, "fetch", standInFetch([401, 200]).impl);
|
|
||||||
try {
|
|
||||||
await c.listShares();
|
|
||||||
assert.equal(writes(occ.calls).length, 1);
|
|
||||||
} finally {
|
|
||||||
f.mock.restore();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 1253/AC4: given the login stays refused, when the loop ticks every minute, then the refusal
|
|
||||||
// is said once, the shares are asked only after 5, 10, 20, 40 and then every 60 minutes, the
|
|
||||||
// ceiling is said once more, and one line says when the login is accepted again.
|
|
||||||
test("a refusal that stays is said once, backed off to an hour, and its end is said", async () => {
|
|
||||||
const said: string[] = [];
|
|
||||||
const err = mock.method(console, "error", (...a: unknown[]) => { said.push(a.join(" ")); });
|
|
||||||
const log = mock.method(console, "log", (...a: unknown[]) => { said.push(a.join(" ")); });
|
|
||||||
try {
|
|
||||||
let refused = true;
|
|
||||||
let polls = 0;
|
|
||||||
const askedAt: number[] = [];
|
|
||||||
let clock = 0;
|
|
||||||
const poll = async (): Promise<void> => {
|
|
||||||
polls += 1;
|
|
||||||
askedAt.push(clock / 60_000);
|
|
||||||
if (refused) throw new LoginRefusedError(401, "mesh-admin", "Unauthorised");
|
|
||||||
};
|
|
||||||
const backoff = new Backoff(5 * 60_000, 60 * 60_000);
|
|
||||||
for (let minute = 0; minute <= 200; minute++) {
|
|
||||||
clock = minute * 60_000;
|
|
||||||
await pollSharesGuarded({} as any, backoff, poll, () => clock);
|
|
||||||
}
|
|
||||||
assert.deepEqual(askedAt, [0, 5, 15, 35, 75, 135, 195]);
|
|
||||||
assert.equal(said.length, 2, said.join("\n"));
|
|
||||||
assert.match(said[0], /refused the login of mesh-admin \(401: Unauthorised\); not asking again for 5 min/);
|
|
||||||
assert.match(said[1], /still; asking once an hour/);
|
|
||||||
|
|
||||||
refused = false;
|
|
||||||
clock = 260 * 60_000;
|
|
||||||
await pollSharesGuarded({} as any, backoff, poll, () => clock);
|
|
||||||
assert.equal(said[2], "[nextcloud] shares: the login is accepted again");
|
|
||||||
const before = polls;
|
|
||||||
clock += 60_000;
|
|
||||||
await pollSharesGuarded({} as any, backoff, poll, () => clock);
|
|
||||||
assert.equal(polls, before + 1, "every tick again once it passes");
|
|
||||||
assert.equal(said.length, 3);
|
|
||||||
} finally {
|
|
||||||
err.mock.restore();
|
|
||||||
log.mock.restore();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 1253/AC5: occ passes an environment variable by name alone, its value through the docker
|
|
||||||
// CLI's environment, so the password is on no argv.
|
|
||||||
test("occ passes OC_PASS by name, its value only in docker's environment", () => {
|
|
||||||
const dir = mkdtempSync(join(tmpdir(), "nextcloud-1253-"));
|
|
||||||
const record = join(dir, "record");
|
|
||||||
writeFileSync(join(dir, "docker"), `#!/bin/sh\nprintf '%s\\n' "$*" > ${record}\nprintf '%s' "$OC_PASS" >> ${record}\n`);
|
|
||||||
chmodSync(join(dir, "docker"), 0o755);
|
|
||||||
const path = process.env.PATH;
|
|
||||||
process.env.PATH = `${dir}:${path}`;
|
|
||||||
try {
|
|
||||||
client().occ(["user:resetpassword", "--password-from-env", "mesh-admin"], { env: { OC_PASS: PASSWORD } });
|
|
||||||
} finally {
|
|
||||||
process.env.PATH = path;
|
|
||||||
}
|
|
||||||
const [argv, env] = readFileSync(record, "utf8").split("\n");
|
|
||||||
assert.equal(argv, "exec -e OC_PASS -u www-data nextcloud php occ user:resetpassword --password-from-env mesh-admin");
|
|
||||||
assert.equal(env, PASSWORD);
|
|
||||||
});
|
|
||||||
|
|
||||||
// 1253/AC6: an empty setting is the manifest's default, never an empty account name.
|
|
||||||
test("an empty user setting falls back to the manifest's account", () => {
|
|
||||||
const dir = mkdtempSync(join(tmpdir(), "nextcloud-1253-"));
|
|
||||||
writeFileSync(join(dir, "config.json"), JSON.stringify({ user: "", container: "", url: "" }));
|
|
||||||
writeFileSync(join(dir, "admin.secret"), PASSWORD);
|
|
||||||
const c: any = NextcloudClient.fromEnv({
|
|
||||||
MESH_NEXTCLOUD_CONFIG_FILE: join(dir, "config.json"),
|
|
||||||
MESH_NEXTCLOUD_ADMIN_PASSWORD_FILE: join(dir, "admin.secret"),
|
|
||||||
MESH_NEXTCLOUD_ADMIN_USER: "mesh-admin",
|
|
||||||
MESH_NEXTCLOUD_URL: "http://127.0.0.1:20003",
|
|
||||||
});
|
|
||||||
assert.equal(c.adminUser, "mesh-admin");
|
|
||||||
assert.equal(c.container, "nextcloud");
|
|
||||||
assert.equal(c.ocsUrl, "http://127.0.0.1:20003");
|
|
||||||
});
|
|
||||||
|
|
||||||
// 1253/AC7: the repair touches only the module's own account: a refusal under a user setting
|
|
||||||
// naming another account (the operator's) runs no occ write and throws LoginRefusedError.
|
|
||||||
test("a refusal under another account's name writes nothing", async () => {
|
|
||||||
const c = client("jochens");
|
|
||||||
const occ = standInOcc(true);
|
|
||||||
mock.method(c, "occ", occ.impl);
|
|
||||||
const web = standInFetch([401]);
|
|
||||||
const f = mock.method(globalThis, "fetch", web.impl);
|
|
||||||
try {
|
|
||||||
await assert.rejects(c.listShares(), (err: any) => {
|
|
||||||
assert.ok(err instanceof LoginRefusedError);
|
|
||||||
assert.match(err.message, /refused the login of jochens .*not repaired: only mesh-admin/);
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
assert.deepEqual(writes(occ.calls), []);
|
|
||||||
} finally {
|
|
||||||
f.mock.restore();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 1253/AC8: a repair whose occ write fails is said as a LoginRefusedError carrying occ's own
|
|
||||||
// message, so the back-off's one loud line says why.
|
|
||||||
test("a failing repair carries occ's message into LoginRefusedError", async () => {
|
|
||||||
const c = client();
|
|
||||||
mock.method(c, "occ", standInOcc(false, { failWrite: "The password policy refused the password" }).impl);
|
|
||||||
const f = mock.method(globalThis, "fetch", standInFetch([401]).impl);
|
|
||||||
try {
|
|
||||||
await assert.rejects(c.listShares(), (err: any) =>
|
|
||||||
err instanceof LoginRefusedError && /repair failed: The password policy refused the password/.test(err.message));
|
|
||||||
} finally {
|
|
||||||
f.mock.restore();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
/** A fake docker on PATH: answers occ like Nextcloud (user:info's refusal on stdout, exit 1). */
|
|
||||||
function fakeDocker(): { dir: string; record: string } {
|
|
||||||
const dir = mkdtempSync(join(tmpdir(), "nextcloud-1253-docker-"));
|
|
||||||
const record = join(dir, "record");
|
|
||||||
writeFileSync(join(dir, "docker"), [
|
|
||||||
"#!/bin/sh",
|
|
||||||
`printf '%s\\n' "$*" >> ${record}`,
|
|
||||||
'case "$*" in',
|
|
||||||
" *' occ user:info '*) echo 'user not found'; exit 1 ;;",
|
|
||||||
"esac",
|
|
||||||
"exit 0",
|
|
||||||
"",
|
|
||||||
].join("\n"));
|
|
||||||
chmodSync(join(dir, "docker"), 0o755);
|
|
||||||
return { dir, record };
|
|
||||||
}
|
|
||||||
|
|
||||||
function withPath<T>(dir: string, fn: () => T): T {
|
|
||||||
const path = process.env.PATH;
|
|
||||||
process.env.PATH = `${dir}:${path}`;
|
|
||||||
try { return fn(); } finally { process.env.PATH = path; }
|
|
||||||
}
|
|
||||||
|
|
||||||
// 1253/AC9: occ's own refusal on stdout with an empty stderr reaches the error, and the real
|
|
||||||
// occ() through a docker answering like Nextcloud makes the missing account.
|
|
||||||
test("occ says a refusal printed on stdout, and the real occ makes the missing account", () => {
|
|
||||||
const docker = fakeDocker();
|
|
||||||
const c = new (NextcloudClient as any)("nextcloud", "http://127.0.0.1:20003", "mesh-admin", PASSWORD);
|
|
||||||
withPath(docker.dir, () => {
|
|
||||||
assert.throws(() => c.occ(["user:info", "mesh-admin", "--output=json"]), /user not found/);
|
|
||||||
const did = c.ensureAdminAccount();
|
|
||||||
assert.equal(did, "created");
|
|
||||||
});
|
|
||||||
const lines = readFileSync(docker.record, "utf8").trim().split("\n");
|
|
||||||
assert.ok(lines.some((l) => l.endsWith("php occ user:add --password-from-env --display-name=Mesh administration mesh-admin")), lines.join("\n"));
|
|
||||||
assert.ok(!lines.some((l) => l.includes("bruteforce")), "the brute-force record is left alone");
|
|
||||||
assert.ok(!lines.join("\n").includes(PASSWORD));
|
|
||||||
});
|
|
||||||
Reference in New Issue
Block a user