fix(skills): never prune shell installers' current-run backups
The four standalone installers pruned the oldest excess stamp directories regardless of origin, so a migration retiring more than five batches destroyed its own rollback material mid-run — the same data loss already fixed for Go via the run-root registry and present in install.js/install.ps1 as currentRunBackupRoots. Every installer now records the stamp directories it creates and pruning only removes earlier-run batches, which is what the changelog already promises.
This commit is contained in:
1 parent
1d1aca5fd1
commit
f7a3e606f7
5 files changed
+303
-34
No files matched your search
@@ -95,8 +95,8 @@ refresh_cache_tree() {
|
||||
backup_skill_dir() {
|
||||
victim="$1"
|
||||
[ -e "$victim" ] || [ -L "$victim" ] || { printf '\n'; return 0; }
|
||||
# Prune before creating this run's archive, so the backup the caller is about
|
||||
# to depend on for rollback can never be a prune candidate.
|
||||
# Prune before creating this run's archive; every stamp this run has already
|
||||
# created is registered, so pruning can only remove earlier-run batches.
|
||||
prune_skill_backups
|
||||
stamp="$(date -u +%Y%m%d-%H%M%S)"
|
||||
name="$(printf '%s' "$victim" | sed 's#[/\\]#-#g; s#^-##')"
|
||||
@@ -110,18 +110,34 @@ backup_skill_dir() {
|
||||
return 1
|
||||
fi
|
||||
done
|
||||
record_current_run_backup_stamp "$backup_root"
|
||||
mkdir -p "$backup_root" || return 1
|
||||
mv "$victim" "$backup" || return 1
|
||||
printf '%s\n' "$backup"
|
||||
}
|
||||
|
||||
# prune_skill_backups keeps only the newest SKILL_BACKUP_KEEP stamped backup
|
||||
# directories under $HOME/.dws/skill-backups, matching Go's skillBackupKeep /
|
||||
# pruneSkillBackups. Glob order is lexicographic and the stamps are UTC
|
||||
# YYYYmmdd-HHMMSS, so the oldest sort first. Best-effort: a removal failure is
|
||||
# reported and never fatal.
|
||||
# directories from earlier runs under $HOME/.dws/skill-backups, matching Go's
|
||||
# skillBackupKeep / pruneSkillBackups. Stamp directories created by the
|
||||
# current process are never pruned (mirroring Go's run-root registry and
|
||||
# install.js's currentRunBackupRoots), so a migration that retires more than
|
||||
# SKILL_BACKUP_KEEP batches stays reversible. Glob order is lexicographic and
|
||||
# the stamps are UTC YYYYmmdd-HHMMSS, so the oldest sort first. Best-effort:
|
||||
# a removal failure is reported and never fatal.
|
||||
SKILL_BACKUP_KEEP=5
|
||||
|
||||
# Stamp directories created by this run, recorded by backup_skill_dir so
|
||||
# pruning can never delete a backup the running migration may still need to
|
||||
# roll back to.
|
||||
CURRENT_RUN_BACKUP_STAMPS=""
|
||||
|
||||
record_current_run_backup_stamp() {
|
||||
case " $CURRENT_RUN_BACKUP_STAMPS " in
|
||||
*" $1 "*) return 0 ;;
|
||||
esac
|
||||
CURRENT_RUN_BACKUP_STAMPS="${CURRENT_RUN_BACKUP_STAMPS} $1"
|
||||
}
|
||||
|
||||
# is_skill_backup_stamp accepts only directory names DWS itself writes: UTC
|
||||
# YYYYmmdd-HHMMSS, with an optional -N collision suffix. Any other entry in
|
||||
# the backup root is foreign (user data, unrelated tooling) and is preserved
|
||||
@@ -153,13 +169,15 @@ prune_skill_backups() {
|
||||
done
|
||||
[ "$prune_total" -gt "$SKILL_BACKUP_KEEP" ] || return 0
|
||||
prune_drop=$((prune_total - SKILL_BACKUP_KEEP))
|
||||
prune_seen=0
|
||||
for prune_entry in "$prune_root"/*; do
|
||||
[ "$prune_drop" -gt 0 ] || break
|
||||
[ -d "$prune_entry" ] || continue
|
||||
is_skill_backup_stamp "${prune_entry##*/}" || continue
|
||||
prune_seen=$((prune_seen + 1))
|
||||
[ "$prune_seen" -le "$prune_drop" ] || break
|
||||
case " $CURRENT_RUN_BACKUP_STAMPS " in
|
||||
*" $prune_entry "*) continue ;;
|
||||
esac
|
||||
rm -rf "$prune_entry" || printf ' ⚠️ 旧 Skill 备份清理失败: %s\n' "$prune_entry" >&2
|
||||
prune_drop=$((prune_drop - 1))
|
||||
done
|
||||
}
|
||||
|
||||
|
||||
@@ -162,8 +162,8 @@ refresh_cache_tree() {
|
||||
backup_skill_dir() {
|
||||
victim="$1"
|
||||
[ -e "$victim" ] || [ -L "$victim" ] || { printf '\n'; return 0; }
|
||||
# Prune before creating this run's archive, so the backup the caller is about
|
||||
# to depend on for rollback can never be a prune candidate.
|
||||
# Prune before creating this run's archive; every stamp this run has already
|
||||
# created is registered, so pruning can only remove earlier-run batches.
|
||||
prune_skill_backups
|
||||
stamp="$(date -u +%Y%m%d-%H%M%S)"
|
||||
name="$(printf '%s' "$victim" | sed 's#[/\\]#-#g; s#^-##')"
|
||||
@@ -179,18 +179,34 @@ backup_skill_dir() {
|
||||
return 1
|
||||
fi
|
||||
done
|
||||
record_current_run_backup_stamp "$backup_root"
|
||||
mkdir -p "$backup_root" || return 1
|
||||
mv "$victim" "$backup" || return 1
|
||||
printf '%s\n' "$backup"
|
||||
}
|
||||
|
||||
# prune_skill_backups keeps only the newest SKILL_BACKUP_KEEP stamped backup
|
||||
# directories under $HOME/.dws/skill-backups, matching Go's skillBackupKeep /
|
||||
# pruneSkillBackups. Glob order is lexicographic and the stamps are UTC
|
||||
# YYYYmmdd-HHMMSS, so the oldest sort first. Best-effort: a removal failure is
|
||||
# reported and never fatal.
|
||||
# directories from earlier runs under $HOME/.dws/skill-backups, matching Go's
|
||||
# skillBackupKeep / pruneSkillBackups. Stamp directories created by the
|
||||
# current process are never pruned (mirroring Go's run-root registry and
|
||||
# install.js's currentRunBackupRoots), so a migration that retires more than
|
||||
# SKILL_BACKUP_KEEP batches stays reversible. Glob order is lexicographic and
|
||||
# the stamps are UTC YYYYmmdd-HHMMSS, so the oldest sort first. Best-effort:
|
||||
# a removal failure is reported and never fatal.
|
||||
SKILL_BACKUP_KEEP=5
|
||||
|
||||
# Stamp directories created by this run, recorded by backup_skill_dir so
|
||||
# pruning can never delete a backup the running migration may still need to
|
||||
# roll back to.
|
||||
CURRENT_RUN_BACKUP_STAMPS=""
|
||||
|
||||
record_current_run_backup_stamp() {
|
||||
case " $CURRENT_RUN_BACKUP_STAMPS " in
|
||||
*" $1 "*) return 0 ;;
|
||||
esac
|
||||
CURRENT_RUN_BACKUP_STAMPS="${CURRENT_RUN_BACKUP_STAMPS} $1"
|
||||
}
|
||||
|
||||
# is_skill_backup_stamp accepts only directory names DWS itself writes: UTC
|
||||
# YYYYmmdd-HHMMSS, with an optional -N collision suffix. Any other entry in
|
||||
# the backup root is foreign (user data, unrelated tooling) and is preserved
|
||||
@@ -222,13 +238,15 @@ prune_skill_backups() {
|
||||
done
|
||||
[ "$prune_total" -gt "$SKILL_BACKUP_KEEP" ] || return 0
|
||||
prune_drop=$((prune_total - SKILL_BACKUP_KEEP))
|
||||
prune_seen=0
|
||||
for prune_entry in "$prune_root"/*; do
|
||||
[ "$prune_drop" -gt 0 ] || break
|
||||
[ -d "$prune_entry" ] || continue
|
||||
is_skill_backup_stamp "${prune_entry##*/}" || continue
|
||||
prune_seen=$((prune_seen + 1))
|
||||
[ "$prune_seen" -le "$prune_drop" ] || break
|
||||
case " $CURRENT_RUN_BACKUP_STAMPS " in
|
||||
*" $prune_entry "*) continue ;;
|
||||
esac
|
||||
rm -rf "$prune_entry" || printf ' ⚠️ 旧 Skill 备份清理失败: %s\n' "$prune_entry" >&2
|
||||
prune_drop=$((prune_drop - 1))
|
||||
done
|
||||
}
|
||||
|
||||
|
||||
@@ -67,6 +67,7 @@ backup_and_remove_skill_dir() {
|
||||
return 1
|
||||
fi
|
||||
done
|
||||
record_current_run_backup_stamp "$(dirname "$_bed_target")"
|
||||
mkdir -p "$(dirname "$_bed_target")" 2>/dev/null || {
|
||||
printf ' ⚠️ 无法创建备份目录,保留原目录 %s\n' "$_bed_dir"
|
||||
return 1
|
||||
@@ -80,13 +81,27 @@ backup_and_remove_skill_dir() {
|
||||
return 1
|
||||
}
|
||||
|
||||
# prune_skill_backups keeps only the newest DWS_SKILL_BACKUP_KEEP stamped backup
|
||||
# directories under $HOME/.dws/skill-backups, matching Go's skillBackupKeep /
|
||||
# pruneSkillBackups. Best-effort: a removal failure is reported, never fatal.
|
||||
# Only safe to call once every backup manifest of this run has been consumed,
|
||||
# because a single transaction can span more than one timestamp.
|
||||
# prune_skill_backups keeps only the newest DWS_SKILL_BACKUP_KEEP stamped
|
||||
# backup directories from earlier runs under $HOME/.dws/skill-backups,
|
||||
# matching Go's skillBackupKeep / pruneSkillBackups. Stamp directories created
|
||||
# by the current process are never pruned (mirroring Go's run-root registry
|
||||
# and install.js's currentRunBackupRoots), so a migration that retires more
|
||||
# than DWS_SKILL_BACKUP_KEEP batches stays reversible. Best-effort: a removal
|
||||
# failure is reported, never fatal.
|
||||
DWS_SKILL_BACKUP_KEEP=5
|
||||
|
||||
# Stamp directories created by this run, recorded by
|
||||
# backup_and_remove_skill_dir so pruning can never delete a backup the
|
||||
# running migration may still need to roll back to.
|
||||
DWS_CURRENT_RUN_BACKUP_STAMPS=""
|
||||
|
||||
record_current_run_backup_stamp() {
|
||||
case " $DWS_CURRENT_RUN_BACKUP_STAMPS " in
|
||||
*" $1 "*) return 0 ;;
|
||||
esac
|
||||
DWS_CURRENT_RUN_BACKUP_STAMPS="${DWS_CURRENT_RUN_BACKUP_STAMPS} $1"
|
||||
}
|
||||
|
||||
# is_skill_backup_stamp accepts only directory names DWS itself writes: UTC
|
||||
# YYYYmmdd-HHMMSS, with an optional -N collision suffix. Any other entry in
|
||||
# the backup root is foreign (user data, unrelated tooling) and is preserved
|
||||
@@ -118,13 +133,15 @@ prune_skill_backups() {
|
||||
done
|
||||
[ "$_psb_total" -gt "$DWS_SKILL_BACKUP_KEEP" ] || return 0
|
||||
_psb_drop=$((_psb_total - DWS_SKILL_BACKUP_KEEP))
|
||||
_psb_seen=0
|
||||
for _psb_entry in "$_psb_root"/*; do
|
||||
[ "$_psb_drop" -gt 0 ] || break
|
||||
[ -d "$_psb_entry" ] || continue
|
||||
is_skill_backup_stamp "${_psb_entry##*/}" || continue
|
||||
_psb_seen=$((_psb_seen + 1))
|
||||
[ "$_psb_seen" -le "$_psb_drop" ] || break
|
||||
case " $DWS_CURRENT_RUN_BACKUP_STAMPS " in
|
||||
*" $_psb_entry "*) continue ;;
|
||||
esac
|
||||
rm -rf "$_psb_entry" || printf ' ⚠️ 旧 Skill 备份清理失败: %s\n' "$_psb_entry"
|
||||
_psb_drop=$((_psb_drop - 1))
|
||||
done
|
||||
}
|
||||
|
||||
|
||||
+25
-8
@@ -85,6 +85,7 @@ backup_and_remove_skill_dir() {
|
||||
return 1
|
||||
fi
|
||||
done
|
||||
record_current_run_backup_stamp "$(dirname "$_bed_target")"
|
||||
mkdir -p "$(dirname "$_bed_target")" 2>/dev/null || {
|
||||
say " ⚠️ 无法创建备份目录,保留原目录 $_bed_dir"
|
||||
return 1
|
||||
@@ -98,13 +99,27 @@ backup_and_remove_skill_dir() {
|
||||
return 1
|
||||
}
|
||||
|
||||
# prune_skill_backups keeps only the newest DWS_SKILL_BACKUP_KEEP stamped backup
|
||||
# directories under $HOME/.dws/skill-backups, matching Go's skillBackupKeep /
|
||||
# pruneSkillBackups. Best-effort: a removal failure is reported, never fatal.
|
||||
# Only safe to call once every backup manifest of this run has been consumed,
|
||||
# because a single transaction can span more than one timestamp.
|
||||
# prune_skill_backups keeps only the newest DWS_SKILL_BACKUP_KEEP stamped
|
||||
# backup directories from earlier runs under $HOME/.dws/skill-backups,
|
||||
# matching Go's skillBackupKeep / pruneSkillBackups. Stamp directories created
|
||||
# by the current process are never pruned (mirroring Go's run-root registry
|
||||
# and install.js's currentRunBackupRoots), so a migration that retires more
|
||||
# than DWS_SKILL_BACKUP_KEEP batches stays reversible. Best-effort: a removal
|
||||
# failure is reported, never fatal.
|
||||
DWS_SKILL_BACKUP_KEEP=5
|
||||
|
||||
# Stamp directories created by this run, recorded by
|
||||
# backup_and_remove_skill_dir so pruning can never delete a backup the
|
||||
# running migration may still need to roll back to.
|
||||
DWS_CURRENT_RUN_BACKUP_STAMPS=""
|
||||
|
||||
record_current_run_backup_stamp() {
|
||||
case " $DWS_CURRENT_RUN_BACKUP_STAMPS " in
|
||||
*" $1 "*) return 0 ;;
|
||||
esac
|
||||
DWS_CURRENT_RUN_BACKUP_STAMPS="${DWS_CURRENT_RUN_BACKUP_STAMPS} $1"
|
||||
}
|
||||
|
||||
# is_skill_backup_stamp accepts only directory names DWS itself writes: UTC
|
||||
# YYYYmmdd-HHMMSS, with an optional -N collision suffix. Any other entry in
|
||||
# the backup root is foreign (user data, unrelated tooling) and is preserved
|
||||
@@ -136,13 +151,15 @@ prune_skill_backups() {
|
||||
done
|
||||
[ "$_psb_total" -gt "$DWS_SKILL_BACKUP_KEEP" ] || return 0
|
||||
_psb_drop=$((_psb_total - DWS_SKILL_BACKUP_KEEP))
|
||||
_psb_seen=0
|
||||
for _psb_entry in "$_psb_root"/*; do
|
||||
[ "$_psb_drop" -gt 0 ] || break
|
||||
[ -d "$_psb_entry" ] || continue
|
||||
is_skill_backup_stamp "${_psb_entry##*/}" || continue
|
||||
_psb_seen=$((_psb_seen + 1))
|
||||
[ "$_psb_seen" -le "$_psb_drop" ] || break
|
||||
case " $DWS_CURRENT_RUN_BACKUP_STAMPS " in
|
||||
*" $_psb_entry "*) continue ;;
|
||||
esac
|
||||
rm -rf "$_psb_entry" || say " ⚠️ 旧 Skill 备份清理失败: $_psb_entry"
|
||||
_psb_drop=$((_psb_drop - 1))
|
||||
done
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,199 @@
|
||||
// Copyright 2026 Alibaba Group
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package scripts_test
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// shellPruneScriptTargets are the standalone installers that embed their own
|
||||
// copy of prune_skill_backups. Each must keep every backup stamp the running
|
||||
// process created, mirroring Go's run-root registry and install.js's
|
||||
// currentRunBackupRoots, so a migration retiring more than the keep limit
|
||||
// stays reversible.
|
||||
var shellPruneScriptTargets = []string{
|
||||
"install.sh",
|
||||
"install-skills.sh",
|
||||
"install-event.sh",
|
||||
"install-devapp.sh",
|
||||
}
|
||||
|
||||
// extractShellFunction pulls a top-level `name() { ... }` definition out of a
|
||||
// script so the shipped logic — not a copy — is what the test exercises.
|
||||
func extractShellFunction(t *testing.T, scriptPath, name string) string {
|
||||
t.Helper()
|
||||
data, err := os.ReadFile(scriptPath)
|
||||
if err != nil {
|
||||
t.Fatalf("read %s: %v", scriptPath, err)
|
||||
}
|
||||
text := string(data)
|
||||
start := strings.Index(text, "\n"+name+"() {")
|
||||
if start < 0 {
|
||||
t.Fatalf("function %s not found in %s", name, scriptPath)
|
||||
}
|
||||
start++
|
||||
end := strings.Index(text[start:], "\n}\n")
|
||||
if end < 0 {
|
||||
t.Fatalf("function %s has no top-level closing brace in %s", name, scriptPath)
|
||||
}
|
||||
return text[start : start+end+3]
|
||||
}
|
||||
|
||||
// runShellPruneScenario builds a sandbox HOME with the given pre-existing
|
||||
// (foreign) stamps, registers the given stamps as created by the current run,
|
||||
// runs the script's own prune_skill_backups, and returns the surviving stamp
|
||||
// directories.
|
||||
func runShellPruneScenario(t *testing.T, scriptPath string, foreignStamps, runStamps []string) []string {
|
||||
t.Helper()
|
||||
home := t.TempDir()
|
||||
backupRoot := filepath.Join(home, ".dws", "skill-backups")
|
||||
for _, stamp := range foreignStamps {
|
||||
if err := os.MkdirAll(filepath.Join(backupRoot, stamp), 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
var driver strings.Builder
|
||||
driver.WriteString("#!/bin/sh\n")
|
||||
driver.WriteString("say() { :; }\n")
|
||||
// The keep limit is a script-level variable outside the extracted
|
||||
// functions; set both spellings the four installers use.
|
||||
driver.WriteString("DWS_SKILL_BACKUP_KEEP=5\n")
|
||||
driver.WriteString("SKILL_BACKUP_KEEP=5\n")
|
||||
driver.WriteString(extractShellFunction(t, scriptPath, "is_skill_backup_stamp"))
|
||||
driver.WriteString("\n")
|
||||
driver.WriteString(extractShellFunction(t, scriptPath, "record_current_run_backup_stamp"))
|
||||
driver.WriteString("\n")
|
||||
driver.WriteString(extractShellFunction(t, scriptPath, "prune_skill_backups"))
|
||||
driver.WriteString("\n")
|
||||
for _, stamp := range runStamps {
|
||||
if err := os.MkdirAll(filepath.Join(backupRoot, stamp), 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
driver.WriteString("record_current_run_backup_stamp \"$HOME/.dws/skill-backups/" + stamp + "\"\n")
|
||||
}
|
||||
driver.WriteString("prune_skill_backups\n")
|
||||
|
||||
driverPath := filepath.Join(t.TempDir(), "prune-driver.sh")
|
||||
if err := os.WriteFile(driverPath, []byte(driver.String()), 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
cmd := exec.Command("sh", driverPath)
|
||||
cmd.Env = append(os.Environ(), "HOME="+home)
|
||||
if out, err := cmd.CombinedOutput(); err != nil {
|
||||
t.Fatalf("prune driver failed: %v\n%s", err, out)
|
||||
}
|
||||
|
||||
entries, err := os.ReadDir(backupRoot)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var surviving []string
|
||||
for _, entry := range entries {
|
||||
if entry.IsDir() {
|
||||
surviving = append(surviving, entry.Name())
|
||||
}
|
||||
}
|
||||
return surviving
|
||||
}
|
||||
|
||||
func assertStampsSurvive(t *testing.T, surviving []string, want ...string) {
|
||||
t.Helper()
|
||||
left := make(map[string]bool)
|
||||
for _, stamp := range surviving {
|
||||
left[stamp] = true
|
||||
}
|
||||
for _, stamp := range want {
|
||||
if !left[stamp] {
|
||||
t.Fatalf("stamp %s must survive pruning, surviving=%v", stamp, surviving)
|
||||
}
|
||||
delete(left, stamp)
|
||||
}
|
||||
for stamp := range left {
|
||||
t.Fatalf("stamp %s must have been pruned, surviving=%v", stamp, surviving)
|
||||
}
|
||||
}
|
||||
|
||||
// TestInstallShellPruneKeepsCurrentRunBackups pins the reversibility contract
|
||||
// the changelog states for every install surface: pruning may only remove
|
||||
// batches from earlier runs. The old shell logic deleted the oldest excess
|
||||
// stamps regardless of origin, so a single migration retiring more than five
|
||||
// batches silently destroyed its own rollback material.
|
||||
func TestInstallShellPruneKeepsCurrentRunBackups(t *testing.T) {
|
||||
if runtime.GOOS == "windows" {
|
||||
t.Skipf("shell installer tests do not run on %s", runtime.GOOS)
|
||||
}
|
||||
for _, name := range shellPruneScriptTargets {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
scriptPath := filepath.Join("..", "..", "scripts", name)
|
||||
if _, err := os.Stat(scriptPath); err != nil {
|
||||
t.Fatalf("stat %s: %v", scriptPath, err)
|
||||
}
|
||||
|
||||
t.Run("run that exceeds the keep limit loses nothing", func(t *testing.T) {
|
||||
var runStamps []string
|
||||
for i := 1; i <= 8; i++ {
|
||||
runStamps = append(runStamps, stampName(i))
|
||||
}
|
||||
surviving := runShellPruneScenario(t, scriptPath, nil, runStamps)
|
||||
assertStampsSurvive(t, surviving, runStamps...)
|
||||
})
|
||||
|
||||
t.Run("only earlier-run batches are pruned", func(t *testing.T) {
|
||||
var foreignStamps []string
|
||||
for i := 1; i <= 6; i++ {
|
||||
foreignStamps = append(foreignStamps, oldStampName(i))
|
||||
}
|
||||
runStamps := []string{stampName(1), stampName(2)}
|
||||
surviving := runShellPruneScenario(t, scriptPath, foreignStamps, runStamps)
|
||||
// Total 8, keep 5: three oldest foreign batches go, the
|
||||
// current run keeps both of its own.
|
||||
assertStampsSurvive(t, surviving,
|
||||
oldStampName(4), oldStampName(5), oldStampName(6),
|
||||
stampName(1), stampName(2))
|
||||
})
|
||||
|
||||
t.Run("foreign-only history still prunes to the keep limit", func(t *testing.T) {
|
||||
var foreignStamps []string
|
||||
for i := 1; i <= 7; i++ {
|
||||
foreignStamps = append(foreignStamps, oldStampName(i))
|
||||
}
|
||||
surviving := runShellPruneScenario(t, scriptPath, foreignStamps, nil)
|
||||
assertStampsSurvive(t, surviving,
|
||||
oldStampName(3), oldStampName(4), oldStampName(5),
|
||||
oldStampName(6), oldStampName(7))
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func stampName(i int) string {
|
||||
return "20260817-0000" + twoDigits(i)
|
||||
}
|
||||
|
||||
func oldStampName(i int) string {
|
||||
return "20200101-0000" + twoDigits(i)
|
||||
}
|
||||
|
||||
func twoDigits(i int) string {
|
||||
if i < 10 {
|
||||
return "0" + string(rune('0'+i))
|
||||
}
|
||||
return string(rune('0'+i/10)) + string(rune('0'+i%10))
|
||||
}
|
||||
Reference in new issue
Block a user