ludic/packages/ludic.update/tests/update_test.ludic

282 lines
13 KiB
Text

# update_test.ludic - the newest version and its notes, the plan (deltas or the whole package), and
# the install step by step, against a fake network, fake children and a stand-in root on disk
import "ludic.update"
import "ludic.base"
program UpdateTest {
numbers float
state UpdateTestState {
net_answer: int = -1
net_text: string = ""
net_urls: []string = null
net_paths: []string = null
net_got: int = 0
proc_code: int = -1
proc_paths: []string = null
proc_args: []string = null
exe: string = ""
}
function fake_platform() -> string { return "macos" }
function fake_exe(update_test_st: UpdateTestState) -> string { return update_test_st.exe }
function fake_pid() -> int { return 4242 }
function fake_ms() -> int { return 0 }
function fake_get(update_test_st: mut UpdateTestState, url: string) -> int {
push(update_test_st.net_urls, url)
push(update_test_st.net_paths, "")
return len(update_test_st.net_urls) - 1
}
function fake_download(update_test_st: mut UpdateTestState, url: string, path: string) -> int {
push(update_test_st.net_urls, url)
push(update_test_st.net_paths, path)
return len(update_test_st.net_urls) - 1
}
function fake_poll(update_test_st: UpdateTestState, h: int) -> int { return update_test_st.net_answer }
function fake_text(update_test_st: UpdateTestState, h: int) -> string { return update_test_st.net_text }
function fake_received(update_test_st: UpdateTestState, h: int) -> int { return update_test_st.net_got }
function fake_free(h: int) -> void { }
function fake_spawn(update_test_st: mut UpdateTestState, path: string, args: []string) -> int {
push(update_test_st.proc_paths, path)
push(update_test_st.proc_args, Text.join(args, " "))
return len(update_test_st.proc_paths) - 1
}
function fake_proc_poll(update_test_st: UpdateTestState, h: int) -> int { return update_test_st.proc_code }
function fake_proc_free(h: int) -> void { }
bind UpdateWorld { platform: fn fake_platform, exe: fn fake_exe, pid: fn fake_pid, now_ms: fn fake_ms }
bind UpdateNet { get: fn fake_get, download: fn fake_download, poll: fn fake_poll, text: fn fake_text, received: fn fake_received, free: fn fake_free }
bind UpdateProc { spawn: fn fake_spawn, poll: fn fake_proc_poll, free: fn fake_proc_free }
function dir() -> string { return Os.temp_dir() + "/ludic-update-test" }
function pkgs() -> string { return dir() + "/packages" }
function hashf() -> string { return dir() + "/hash.txt" }
function fresh(update_st: mut UpdateState, update_test_st: mut UpdateTestState, root: bool) -> void {
update_test_st.net_answer = -1
update_test_st.net_text = ""
update_test_st.net_urls = new []string
update_test_st.net_paths = new []string
update_test_st.net_got = 0
update_test_st.proc_code = -1
update_test_st.proc_paths = new []string
update_test_st.proc_args = new []string
update_test_st.exe = dir() + "/Game.app/Contents/MacOS/Game"
System.run("rm -rf '" + dir() + "'")
Fs.mkdir(dir())
Fs.mkdir(pkgs())
configure(update_st, root)
update_reset(update_st)
}
# whether this copy was installed is worked out when the updater is configured, so a test that
# changes the disk or the executable's place configures again
function configure(update_st: mut UpdateState, root: bool) -> void {
let c = new UpdateConfig
c.feed = "http://feed"
c.app_id = "Game"
c.version = "1.0.0"
if root { c.root = dir() + "/Game.app" }
c.packages = pkgs()
c.scratch = hashf()
update_config(update_st, c)
}
function asset(ty: string, ver: string, size: int) -> string {
let file = "Game-" + ver + "-osx-" + Text.lower(ty) + ".nupkg"
return "{\"PackageId\":\"Game\",\"Type\":\"" + ty + "\",\"Version\":\"" + ver + "\",\"FileName\":\"" + file + "\",\"SHA256\":\"ab" + ver + "\",\"Size\":" + string(size) + "}"
}
# 1.0.0 installed; 1.1.0 and 1.2.0 out, each with a delta
function feed(with_delta_11: bool) -> string {
var s = "{\"Assets\":[" + asset("Full", "1.0.0", 900) + "," + asset("Full", "1.1.0", 1000) + "," + asset("Full", "1.2.0", 1000)
if with_delta_11 { s = s + "," + asset("Delta", "1.1.0", 50) }
return s + "," + asset("Delta", "1.2.0", 60) + "]}"
}
function seed_base() -> void { Fs.write_text(pkgs() + "/Game-1.0.0-osx-full.nupkg", "base") }
function plan_of(update_st: mut UpdateState, text: string) -> bool { return update_plan(update_st, Json.parse(text)) }
function facts_of(update_st: UpdateState, what: int) -> int {
let fs = q_drain(update_facts(update_st))
var n = 0
for i in 0 .. len(fs) { if fs[i].what == what { n += 1 } }
return n
}
function hash_is(h: string) -> void { Fs.write_text(hashf(), h + "\n") }
function last_args(update_test_st: UpdateTestState) -> string { return update_test_st.proc_args[len(update_test_st.proc_args) - 1] }
function last_url(update_test_st: UpdateTestState) -> string { return update_test_st.net_urls[len(update_test_st.net_urls) - 1] }
test "versions compare a number at a time" {
expect(update_newer("0.10.2", "0.9.9"))
expect(not update_newer("0.5.1", "0.5.1"))
expect(update_newer("1.0", "0.99.99"))
expect(not update_newer("0.4.9", "0.5.0"))
}
test "a newer version is shown with its notes, in the language asked for or English" (update_st: mut UpdateState, update_test_st: mut UpdateTestState) {
fresh(update_st, update_test_st, true)
update_check_tick(update_st, false)
expect(last_url(update_test_st) == "http://feed/latest.json")
update_test_st.net_text = "{\"version\":\"1.2.0\",\"notes\":{\"en\":[\"Fish.\",\"Boats.\"],\"tr\":[\"Balik.\"]}}"
update_test_st.net_answer = 1
update_check_tick(update_st, false)
expect(update_fresh(update_st))
expect(update_latest(update_st) == "1.2.0")
update_notes_for(update_st, "tr") # the game's tick keeps them for its language
expect_eq(len(update_notes(update_st, "tr")), 1)
expect_eq(len(update_notes(update_st, "de")), 0) # not kept for German yet
update_notes_for(update_st, "de")
expect_eq(len(update_notes(update_st, "de")), 2) # no German: English
}
test "the version being played is shown only when asked" (update_st: mut UpdateState, update_test_st: mut UpdateTestState) {
fresh(update_st, update_test_st, true)
update_test_st.net_text = "{\"version\":\"1.0.0\",\"notes\":{\"en\":[\"x\"]}}"
update_test_st.net_answer = 1
update_check_tick(update_st, false)
update_check_tick(update_st, false)
expect(not update_shown(update_st))
update_reset(update_st)
update_check_tick(update_st, true)
update_check_tick(update_st, true)
expect_eq(update_ask_state(update_st), UPDATE_CURRENT)
update_reset(update_st)
update_test_st.net_answer = 0
update_check_tick(update_st, true)
update_check_tick(update_st, true)
expect_eq(update_ask_state(update_st), UPDATE_NONE)
}
test "with the base on disk and every delta there, the plan is the chain, oldest first" (update_st: mut UpdateState, update_test_st: mut UpdateTestState) {
fresh(update_st, update_test_st, true)
seed_base()
expect(plan_of(update_st, feed(true)))
expect_eq(update_package_count(update_st), 2)
expect(update_plan_is_delta(update_st, 0))
expect(update_plan_version(update_st, 0) == "1.1.0")
expect(update_plan_version(update_st, 1) == "1.2.0")
expect(update_target(update_st) == "1.2.0")
}
test "no base, or a missing delta, is the whole newest package" (update_st: mut UpdateState, update_test_st: mut UpdateTestState) {
fresh(update_st, update_test_st, true)
expect(plan_of(update_st, feed(true)))
expect_eq(update_package_count(update_st), 1)
expect(not update_plan_is_delta(update_st, 0))
expect(update_plan_file(update_st, 0) == "Game-1.2.0-osx-full.nupkg")
seed_base()
expect(plan_of(update_st, feed(false)))
expect_eq(update_package_count(update_st), 1)
}
test "nothing newer is no plan" (update_st: mut UpdateState, update_test_st: mut UpdateTestState) {
fresh(update_st, update_test_st, true)
expect(not plan_of(update_st, "{\"Assets\":[" + asset("Full", "1.0.0", 900) + "]}"))
expect(not plan_of(update_st, "{}"))
}
test "a copy Velopack did not install does not start" (update_st: mut UpdateState, update_test_st: mut UpdateTestState) {
fresh(update_st, update_test_st, false)
expect(not update_installed(update_st))
expect(not update_start(update_st))
expect_eq(update_error(update_st), UPDATE_ERR_NOT_INSTALLED)
Fs.mkdir(dir() + "/Game.app")
Fs.mkdir(dir() + "/Game.app/Contents")
Fs.mkdir(dir() + "/Game.app/Contents/MacOS")
Fs.write_text(dir() + "/Game.app/Contents/MacOS/UpdateMac", "x")
configure(update_st, false)
expect(update_installed(update_st))
update_test_st.exe = "/private/var/folders/x/AppTranslocation/y/Game.app/Contents/MacOS/Game"
configure(update_st, false)
expect(not update_installed(update_st))
}
test "the whole package: fetched, checked, applied, and the game told to go" (update_st: mut UpdateState, update_test_st: mut UpdateTestState) {
fresh(update_st, update_test_st, true)
expect(update_start(update_st))
expect(last_url(update_test_st) == "http://feed/releases.osx.json")
update_test_st.net_text = feed(true)
update_test_st.net_answer = 1
update_tick(update_st)
expect_eq(update_state(update_st), UPDATE_FETCH)
expect_eq(facts_of(update_st, UPDATE_STARTED), 1)
expect(last_url(update_test_st) == "http://feed/Game-1.2.0-osx-full.nupkg")
update_test_st.net_got = 500
expect(update_progress(update_st) >= 490 and update_progress(update_st) <= 510)
update_tick(update_st)
expect_eq(update_state(update_st), UPDATE_CHECK)
expect(Text.contains(last_args(update_test_st), "shasum -a 256"))
hash_is("AB1.2.0")
update_test_st.proc_code = 0
update_tick(update_st)
expect_eq(update_state(update_st), UPDATE_APPLY)
expect(Text.contains(last_args(update_test_st), "apply --silent --waitPid 4242 --package"))
expect(Text.contains(last_args(update_test_st), "--rootDir"))
expect(Text.ends_with(update_test_st.proc_paths[len(update_test_st.proc_paths) - 1], "Game.app/Contents/MacOS/UpdateMac"))
expect_eq(facts_of(update_st, UPDATE_APPLYING), 1)
}
test "a wrong hash stops it and deletes the file" (update_st: mut UpdateState, update_test_st: mut UpdateTestState) {
fresh(update_st, update_test_st, true)
update_start(update_st)
update_test_st.net_text = feed(true)
update_test_st.net_answer = 1
update_tick(update_st)
Fs.write_text(pkgs() + "/Game-1.2.0-osx-full.nupkg", "junk")
update_tick(update_st)
hash_is("FFFF")
update_test_st.proc_code = 0
update_tick(update_st)
expect(update_failed(update_st))
expect_eq(update_error(update_st), UPDATE_ERR_CORRUPT)
expect(not Fs.exists(pkgs() + "/Game-1.2.0-osx-full.nupkg"))
expect_eq(facts_of(update_st, UPDATE_STOPPED), 1)
}
test "a failed download stops it" (update_st: mut UpdateState, update_test_st: mut UpdateTestState) {
fresh(update_st, update_test_st, true)
update_start(update_st)
update_test_st.net_text = feed(true)
update_test_st.net_answer = 1
update_tick(update_st)
update_test_st.net_answer = 0
update_tick(update_st)
expect_eq(update_error(update_st), UPDATE_ERR_DOWNLOAD)
}
test "the delta chain: each fetched, checked and patched onto the last, then applied" (update_st: mut UpdateState, update_test_st: mut UpdateTestState) {
fresh(update_st, update_test_st, true)
seed_base()
update_start(update_st)
update_test_st.net_text = feed(true)
update_test_st.net_answer = 1
update_tick(update_st)
update_tick(update_st)
hash_is("AB1.1.0")
update_test_st.proc_code = 0
update_tick(update_st)
expect_eq(update_state(update_st), UPDATE_PATCH)
expect(Text.contains(last_args(update_test_st), "--old " + pkgs() + "/Game-1.0.0-osx-full.nupkg"))
expect(Text.contains(last_args(update_test_st), "--output " + pkgs() + "/Game-1.1.0-osx-full.nupkg"))
Fs.write_text(pkgs() + "/Game-1.1.0-osx-full.nupkg", "patched")
update_tick(update_st)
expect_eq(update_state(update_st), UPDATE_FETCH)
expect_eq(update_index(update_st), 1)
update_tick(update_st)
hash_is("AB1.2.0")
update_tick(update_st)
Fs.write_text(pkgs() + "/Game-1.2.0-osx-full.nupkg", "patched")
update_tick(update_st)
expect_eq(update_state(update_st), UPDATE_APPLY)
expect(Text.contains(last_args(update_test_st), "--package " + pkgs() + "/Game-1.2.0-osx-full.nupkg"))
}
test "a package already on disk at its size is checked, not fetched again" (update_st: mut UpdateState, update_test_st: mut UpdateTestState) {
fresh(update_st, update_test_st, true)
update_start(update_st)
update_test_st.net_text = feed(true)
update_test_st.net_answer = 1
Fs.write_text(pkgs() + "/Game-1.2.0-osx-full.nupkg", Text.repeat("x", 1000))
update_tick(update_st)
expect_eq(update_state(update_st), UPDATE_CHECK)
}
}