-- Kør: luajit tests/test_adRuns.lua (fra repo-roden) -- Tester run-modellen (rene funktioner) og XML-laget mod en XMLFile-mock. dofile("FS25_ADSmartPickup/adRuns.lua") local failures = 0 local function check(name, actual, expected) if actual == expected then print("OK " .. name) else failures = failures + 1 print(string.format("FAIL %s: forventede %s, fik %s", name, tostring(expected), tostring(actual))) end end -- R1: opret run -> løbenummer, standardværdier; input-store røres ikke local empty = ADRuns.newStore() local s1, cow = ADRuns.createRun(empty, "Cow Feed") check("R1 id 1", cow.id, 1) check("R1 navn", cow.name, "Cow Feed") check("R1 auto-varer som standard", cow.ingredientMode, "auto") check("R1 loops 0", cow.loops, 0) check("R1 tom Wait-pulje", cow.waitPoolGroup, "") check("R1 input-store urørt", #empty.runs, 0) check("R1 nextId tæller op", s1.nextId, 2) local s2, pig = ADRuns.createRun(s1, "Pig Feed") check("R1 andet run får id 2", pig.id, 2) check("R1 to runs", #s2.runs, 2) -- R2: opdatér felter; ukendt mode ignoreres; loops klippes local s3 = ADRuns.updateRun(s2, 1, {name = "Cow 1", targetWayPointId = 94, ingredientMode = "manual", ingredients = {"STRAW", "FORAGE"}, waitPoolGroup = "Cow Wait", loops = 150}) local run1 = ADRuns.getRun(s3, 1) check("R2 navn", run1.name, "Cow 1") check("R2 mål", run1.targetWayPointId, 94) check("R2 manual", run1.ingredientMode, "manual") check("R2 varer", table.concat(run1.ingredients, ","), "STRAW,FORAGE") check("R2 pulje", run1.waitPoolGroup, "Cow Wait") check("R2 loops klippet til 99", run1.loops, 99) check("R2 gammel store urørt", ADRuns.getRun(s2, 1).name, "Cow Feed") local s3b = ADRuns.updateRun(s3, 1, {ingredientMode = "bogus", loops = -5}) check("R2 ukendt mode ignoreres", ADRuns.getRun(s3b, 1).ingredientMode, "manual") check("R2 loops under 0 -> 0", ADRuns.getRun(s3b, 1).loops, 0) check("R2 ukendt run -> uændret", #ADRuns.updateRun(s3, 99, {name = "x"}).runs, 2) check("R2 targetWayPointId=false rydder målet", ADRuns.getRun(ADRuns.updateRun(s3, 1, {targetWayPointId = false}), 1).targetWayPointId, nil) check("R2 nil rører ikke målet", ADRuns.getRun(ADRuns.updateRun(s3, 1, {name = "y"}), 1).targetWayPointId, 94) -- R3: tildel traktor; samme traktor flyttes mellem runs local s4, prev = ADRuns.assignVehicle(s3, 1, "vehicleA") check("R3 tildelt", ADRuns.getRun(s4, 1).vehicleIds[1], "vehicleA") check("R3 ingen tidligere", prev, nil) check("R3 opslag", ADRuns.findRunByVehicle(s4, "vehicleA").id, 1) local s5, prev2 = ADRuns.assignVehicle(s4, 2, "vehicleA") check("R3 flyttet fra run 1", prev2, 1) check("R3 væk fra run 1", #ADRuns.getRun(s5, 1).vehicleIds, 0) check("R3 i run 2", ADRuns.getRun(s5, 2).vehicleIds[1], "vehicleA") local s5b, prev3 = ADRuns.assignVehicle(s5, 2, "vehicleA") check("R3 gen-tildeling samme run -> ingen dublet", #ADRuns.getRun(s5b, 2).vehicleIds, 1) check("R3 gen-tildeling -> ingen 'flyttet fra'", prev3, nil) local s6 = ADRuns.unassignVehicle(s5, "vehicleA") check("R3 fjernet", ADRuns.findRunByVehicle(s6, "vehicleA"), nil) check("R3 ukendt run ved tildeling -> uændret", ADRuns.findRunByVehicle(ADRuns.assignVehicle(s6, 42, "vehicleB"), "vehicleB"), nil) -- R4: slet run local s7 = ADRuns.deleteRun(s5, 1) check("R4 ét run tilbage", #s7.runs, 1) check("R4 det rigtige", s7.runs[1].id, 2) check("R4 nextId bevares", s7.nextId, 3) -- R5: records frem og tilbage local records = ADRuns.toRecords(s5) check("R5 target som tal", records[1].target, 94) check("R5 varer som streng", records[1].ingredients, "STRAW FORAGE") check("R5 køretøjer", records[2].vehicleIds[1], "vehicleA") local back = ADRuns.fromRecords(records) check("R5 to runs tilbage", #back.runs, 2) check("R5 nextId = højeste + 1", back.nextId, 3) check("R5 varer parset", ADRuns.getRun(back, 1).ingredients[2], "FORAGE") check("R5 pulje", ADRuns.getRun(back, 1).waitPoolGroup, "Cow Wait") check("R5 target 0 -> nil", ADRuns.getRun(back, 2).targetWayPointId, nil) check("R5 køretøj", ADRuns.findRunByVehicle(back, "vehicleA").id, 2) -- R6: defekte poster springes over; dubletter af id ignoreres local dirty = ADRuns.fromRecords({ {id = "x", name = "Broken"}, {id = 5, name = "", }, {id = 7, name = "Ok", target = "12", ingredientMode = "weird", ingredients = "", waitPool = nil, loops = "3", vehicleIds = {}}, {id = 7, name = "Dublet"}, }) check("R6 kun den gyldige", #dirty.runs, 1) check("R6 target parses fra streng", dirty.runs[1].targetWayPointId, 12) check("R6 ukendt mode -> auto", dirty.runs[1].ingredientMode, "auto") check("R6 tom varestreng -> tom liste", #dirty.runs[1].ingredients, 0) check("R6 manglende pulje -> tom streng", dirty.runs[1].waitPoolGroup, "") check("R6 loops fra streng", dirty.runs[1].loops, 3) check("R6 nextId", dirty.nextId, 8) check("R6 tom liste", #ADRuns.fromRecords(nil).runs, 0) check("R6 parseIngredients trimmer", #ADRuns.parseIngredients(" STRAW FORAGE "), 2) -- R7: "kør hjem under X %" — standard 50, klippes til 10..90, overlever records local r7, r7run = ADRuns.createRun(ADRuns.newStore(), "Tærskel") check("R7 standard 50 %", r7run.returnBelowPercent, 50) check("R7 sættes", ADRuns.getRun(ADRuns.updateRun(r7, r7run.id, {returnBelowPercent = 30}), r7run.id).returnBelowPercent, 30) check("R7 klippes til 90", ADRuns.getRun(ADRuns.updateRun(r7, r7run.id, {returnBelowPercent = 150}), r7run.id).returnBelowPercent, 90) check("R7 klippes til 10", ADRuns.getRun(ADRuns.updateRun(r7, r7run.id, {returnBelowPercent = 0}), r7run.id).returnBelowPercent, 10) local r7rec = ADRuns.toRecords(ADRuns.updateRun(r7, r7run.id, {returnBelowPercent = 25})) check("R7 i records", r7rec[1].returnBelow, 25) check("R7 tilbage fra records", ADRuns.fromRecords(r7rec).runs[1].returnBelowPercent, 25) check("R7 manglende i gammel fil -> 50", ADRuns.fromRecords({{id = 1, name = "Gammel"}}).runs[1].returnBelowPercent, 50) -- R8: flere mål pr. run — liste + første som "primært" mål (bagudkompatibelt); toggle; records local r8, r8run = ADRuns.createRun(ADRuns.newStore(), "Kvæg") check("R8 ingen mål", #r8run.targetWayPointIds, 0) r8 = ADRuns.updateRun(r8, r8run.id, {targetWayPointIds = {94, 99}}) check("R8 to mål", table.concat(ADRuns.getRun(r8, r8run.id).targetWayPointIds, ","), "94,99") check("R8 primært mål = første", ADRuns.getRun(r8, r8run.id).targetWayPointId, 94) r8 = ADRuns.toggleTarget(r8, r8run.id, 94) check("R8 toggle fjerner", table.concat(ADRuns.getRun(r8, r8run.id).targetWayPointIds, ","), "99") check("R8 primært følger med", ADRuns.getRun(r8, r8run.id).targetWayPointId, 99) r8 = ADRuns.toggleTarget(r8, r8run.id, 120) check("R8 toggle tilføjer", table.concat(ADRuns.getRun(r8, r8run.id).targetWayPointIds, ","), "99,120") check("R8 gammel enkelt-sætning = liste med ét", table.concat(ADRuns.getRun(ADRuns.updateRun(r8, r8run.id, {targetWayPointId = 7}), r8run.id).targetWayPointIds, ","), "7") check("R8 false rydder listen", #ADRuns.getRun(ADRuns.updateRun(r8, r8run.id, {targetWayPointId = false}), r8run.id).targetWayPointIds, 0) local r8rec = ADRuns.toRecords(r8) check("R8 records: targets-streng", r8rec[1].targets, "99 120") check("R8 records: target = første (gammelt felt)", r8rec[1].target, 99) check("R8 tilbage fra records", table.concat(ADRuns.fromRecords(r8rec).runs[1].targetWayPointIds, ","), "99,120") check("R8 gammel fil med kun target", table.concat(ADRuns.fromRecords({{id = 1, name = "Gl", target = 55}}).runs[1].targetWayPointIds, ","), "55") -- ==== XML-laget mod en flad XMLFile-mock (nøgle -> værdi) ==== dofile("FS25_ADSmartPickup/adRunsStorage.lua") local function makeXmlMock(values) local xml = {values = values or {}, saved = false, deleted = false} xml.setInt = function(self, key, value) self.values[key] = value end xml.setString = function(self, key, value) self.values[key] = value end xml.getInt = function(self, key) return tonumber(self.values[key]) end xml.getString = function(self, key) local v = self.values[key]; return v ~= nil and tostring(v) or nil end -- iterate: kalder callback for "prefix(0)", "prefix(1)", ... så længe en nøgle med den stamme findes xml.iterate = function(self, prefix, callback) local index = 0 while true do local key = string.format("%s(%d)", prefix, index) local found = false for k in pairs(self.values) do if k:sub(1, #key) == key then found = true break end end if not found then return end callback(index + 1, key) index = index + 1 end end xml.save = function(self) self.saved = true end xml.delete = function(self) self.deleted = true end return xml end -- X1: records skrives som attributter + vehicle-underelementer local out = makeXmlMock() ADRunsStorage.writeRecords(out, ADRuns.toRecords(s5)) check("X1 version", out.values["ADSmartPickupRuns#version"], 3) check("X1 run 0 navn", out.values["ADSmartPickupRuns.run(0)#name"], "Cow 1") check("X1 run 0 target", out.values["ADSmartPickupRuns.run(0)#target"], 94) check("X1 run 0 varer", out.values["ADSmartPickupRuns.run(0)#ingredients"], "STRAW FORAGE") check("X1 run 0 pulje", out.values["ADSmartPickupRuns.run(0)#waitPool"], "Cow Wait") check("X1 run 1 køretøj", out.values["ADSmartPickupRuns.run(1).vehicle(0)#id"], "vehicleA") -- X2: samme data læses tilbage local records2 = ADRunsStorage.readRecords(makeXmlMock(out.values)) check("X2 to poster", #records2, 2) check("X2 id", records2[1].id, 1) check("X2 køretøj", records2[2].vehicleIds[1], "vehicleA") check("X2 loops", records2[1].loops, 99) check("X2 round-trip", ADRuns.findRunByVehicle(ADRuns.fromRecords(records2), "vehicleA").id, 2) -- X3: save/load via XMLFile-globalen; ingen savegame-mappe -> tom store, intet skrives Logging = Logging or {warning = function() end, info = function() end} local created, loaded = nil, nil XMLFile = { create = function(_, path, root) created = makeXmlMock(); created.path = path; created.root = root; return created end, load = function(_, path) loaded = makeXmlMock(created and created.values or {}); loaded.path = path; return loaded end, } fileExists = function(path) return created ~= nil and created.path == path end g_currentMission = {missionInfo = {savegameDirectory = "/tmp/sg1"}} check("X3 gemt", ADRunsStorage.save(s5), true) check("X3 sti", created.path, "/tmp/sg1/ADSmartPickup_runs.xml") check("X3 rodelement", created.root, "ADSmartPickupRuns") check("X3 save+delete kaldt", created.saved and created.deleted, true) check("X3 indlæst", #ADRunsStorage.load().runs, 2) check("X3 læse-handle lukket", loaded.deleted, true) g_currentMission = {missionInfo = {}} check("X3 uden savegame -> false", ADRunsStorage.save(s5), false) check("X3 uden savegame -> tom store", #ADRunsStorage.load().runs, 0) g_currentMission = {missionInfo = {savegameDirectory = "/tmp/sg-ny"}} check("X3 fil mangler -> tom store", #ADRunsStorage.load().runs, 0) -- R-v2: udkørsels-run — kind, kilder, varer med politik, tærskler, runde; XML v2 + v1-migration local o1, out = ADRuns.createRun(ADRuns.newStore(), "Sugar out", "outbound") check("Rv2 kind", out.kind, "outbound") local _, plain = ADRuns.createRun(ADRuns.newStore(), "x") check("Rv2 supply som standard", plain.kind, "supply") check("Rv2 sellAt 90", out.sellAtPercent, 90) check("Rv2 storeAbove 80", out.storeAbovePercent, 80) check("Rv2 runde fra", out.collectRound, false) local o2 = ADRuns.toggleSource(o1, out.id, 12694) o2 = ADRuns.toggleSource(o2, out.id, 12386) check("Rv2 to kilder", table.concat(ADRuns.getRun(o2, out.id).sourceWayPointIds, ","), "12694,12386") check("Rv2 toggle fjerner", #ADRuns.getRun(ADRuns.toggleSource(o2, out.id, 12694), out.id).sourceWayPointIds, 1) local o3 = ADRuns.setProductPolicy(o2, out.id, "SUGAR", "sell") o3 = ADRuns.setProductPolicy(o3, out.id, "FORAGE", "store") check("Rv2 politik SUGAR", ADRuns.getProductPolicy(ADRuns.getRun(o3, out.id), "SUGAR"), "sell") check("Rv2 politik ukendt = off", ADRuns.getProductPolicy(ADRuns.getRun(o3, out.id), "STRAW"), "off") check("Rv2 ugyldig politik ignoreres", ADRuns.getProductPolicy(ADRuns.getRun(ADRuns.setProductPolicy(o3, out.id, "SUGAR", "bogus"), out.id), "SUGAR"), "sell") check("Rv2 off fjerner", ADRuns.getProductPolicy(ADRuns.getRun(ADRuns.setProductPolicy(o3, out.id, "SUGAR", "off"), out.id), "SUGAR"), "off") check("Rv2 gammel store urørt", #ADRuns.getRun(o2, out.id).products, 0) local o4 = ADRuns.updateRun(o3, out.id, {sellAtPercent = 120, storeAbovePercent = 10, collectRound = true}) check("Rv2 sellAt klippet 100", ADRuns.getRun(o4, out.id).sellAtPercent, 100) check("Rv2 storeAbove klippet 50", ADRuns.getRun(o4, out.id).storeAbovePercent, 50) check("Rv2 runde til", ADRuns.getRun(o4, out.id).collectRound, true) check("Rv2 serialiser", ADRuns.serializeProducts(ADRuns.getRun(o3, out.id).products), "SUGAR:sell FORAGE:store") local parsed = ADRuns.parseProducts("SUGAR:sell FORAGE:store BAD BAD:bogus") check("Rv2 parse antal", #parsed, 2) check("Rv2 parse politik", parsed[2].policy, "store") local rt = ADRuns.fromRecords(ADRuns.toRecords(o4)) local rtRun = ADRuns.getRun(rt, out.id) check("Rv2 roundtrip kind", rtRun.kind, "outbound") check("Rv2 roundtrip kilder", table.concat(rtRun.sourceWayPointIds, ","), "12694,12386") check("Rv2 roundtrip products", ADRuns.serializeProducts(rtRun.products), "SUGAR:sell FORAGE:store") check("Rv2 roundtrip sellAt", rtRun.sellAtPercent, 100) check("Rv2 roundtrip runde", rtRun.collectRound, true) local v1 = ADRuns.fromRecords({{id = 7, name = "Old", target = 94, targets = "94", ingredientMode = "auto", ingredients = "", waitPool = "", loops = 0, returnBelow = 50, vehicleIds = {}}}) check("Rv2 v1 -> supply", ADRuns.getRun(v1, 7).kind, "supply") check("Rv2 v1 tomme kilder", #ADRuns.getRun(v1, 7).sourceWayPointIds, 0) check("Rv2 v1 sellAt standard", ADRuns.getRun(v1, 7).sellAtPercent, 90) -- XML-laget skriver/læser v2-felterne do local written = {} local xml = { setInt = function(_, k, v) written[k] = v end, setString = function(_, k, v) written[k] = v end, getInt = function(_, k) return written[k] end, getString = function(_, k) return written[k] end, iterate = function(_, prefix, fn) local i = 0 while written[string.format("%s(%d)#id", prefix, i)] ~= nil do fn(i, string.format("%s(%d)", prefix, i)); i = i + 1 end end, } ADRunsStorage.writeRecords(xml, ADRuns.toRecords(o4)) check("Rv2 xml version 2", written["ADSmartPickupRuns#version"], 3) check("Rv2 xml kind", written["ADSmartPickupRuns.run(0)#kind"], "outbound") check("Rv2 xml sources", written["ADSmartPickupRuns.run(0)#sources"], "12694 12386") check("Rv2 xml products", written["ADSmartPickupRuns.run(0)#products"], "SUGAR:sell FORAGE:store") check("Rv2 xml collectRound", written["ADSmartPickupRuns.run(0)#collectRound"], 1) local back = ADRuns.fromRecords(ADRunsStorage.readRecords(xml)) check("Rv2 xml læs sellAt", ADRuns.getRun(back, out.id).sellAtPercent, 100) check("Rv2 xml læs runde", ADRuns.getRun(back, out.id).collectRound, true) end -- R-v3: bygninger som kilder/mål (id-lister); XML v3; v2-fil uden bygninger giver tomme lister local b1, flow = ADRuns.createRun(ADRuns.newStore(), "Milk", "outbound") check("Rv3 tomme kilde-bygninger", #flow.sourceBuildingIds, 0) check("Rv3 tomme mål-bygninger", #flow.targetBuildingIds, 0) local b2 = ADRuns.toggleSourceBuilding(b1, flow.id, "barn1") b2 = ADRuns.toggleSourceBuilding(b2, flow.id, "barn2") check("Rv3 to kilde-bygninger", table.concat(ADRuns.getRun(b2, flow.id).sourceBuildingIds, ","), "barn1,barn2") check("Rv3 toggle fjerner", #ADRuns.getRun(ADRuns.toggleSourceBuilding(b2, flow.id, "barn1"), flow.id).sourceBuildingIds, 1) local b3 = ADRuns.toggleTargetBuilding(b2, flow.id, "mill") check("Rv3 mål-bygning", ADRuns.getRun(b3, flow.id).targetBuildingIds[1], "mill") check("Rv3 gammel store urørt", #ADRuns.getRun(b2, flow.id).targetBuildingIds, 0) local b4 = ADRuns.updateRun(b3, flow.id, {sourceBuildingIds = {"shed"}, targetBuildingIds = {}}) check("Rv3 updateRun sætter lister", ADRuns.getRun(b4, flow.id).sourceBuildingIds[1], "shed") check("Rv3 updateRun tømmer", #ADRuns.getRun(b4, flow.id).targetBuildingIds, 0) local rt3 = ADRuns.fromRecords(ADRuns.toRecords(b3)) check("Rv3 roundtrip kilder", table.concat(ADRuns.getRun(rt3, flow.id).sourceBuildingIds, ","), "barn1,barn2") check("Rv3 roundtrip mål", ADRuns.getRun(rt3, flow.id).targetBuildingIds[1], "mill") check("Rv3 XML_VERSION", ADRuns.XML_VERSION, 3) local v2 = ADRuns.fromRecords({{id = 3, name = "V2", kind = "outbound", sources = "12694", products = "SUGAR:sell", vehicleIds = {}}}) check("Rv3 v2 rå markør bevares", ADRuns.getRun(v2, 3).sourceWayPointIds[1], 12694) check("Rv3 v2 ingen bygninger", #ADRuns.getRun(v2, 3).sourceBuildingIds, 0) do local written = {} local xml = { setInt = function(_, k, v) written[k] = v end, setString = function(_, k, v) written[k] = v end, getInt = function(_, k) return written[k] end, getString = function(_, k) return written[k] end, iterate = function(_, prefix, fn) local i = 0 while written[string.format("%s(%d)#id", prefix, i)] ~= nil do fn(i, string.format("%s(%d)", prefix, i)); i = i + 1 end end, } ADRunsStorage.writeRecords(xml, ADRuns.toRecords(b3)) check("Rv3 xml version 3", written["ADSmartPickupRuns#version"], 3) check("Rv3 xml sourceBuildings", written["ADSmartPickupRuns.run(0)#sourceBuildings"], "barn1 barn2") check("Rv3 xml targetBuildings", written["ADSmartPickupRuns.run(0)#targetBuildings"], "mill") local back = ADRuns.fromRecords(ADRunsStorage.readRecords(xml)) check("Rv3 xml læs kilder", table.concat(ADRuns.getRun(back, flow.id).sourceBuildingIds, ","), "barn1,barn2") end -- RN: køretøjsnavne efter forløbet — "Navn 1", "Navn 2" … i medlemsrækkefølge; ét køretøj = bare navnet do local store, runA = ADRuns.createRun(ADRuns.newStore(), "Milk Collect", "outbound") store = ADRuns.assignVehicle(store, runA.id, "v1") store = ADRuns.assignVehicle(store, runA.id, "v2") local runB store, runB = ADRuns.createRun(store, "Milk Salg", "outbound") store = ADRuns.assignVehicle(store, runB.id, "v3") local names = ADRuns.vehicleNames(store) check("RN to i samme forløb: 1", names.v1, "Milk Collect 1") check("RN to i samme forløb: 2", names.v2, "Milk Collect 2") check("RN alene i forløb = navnet", names.v3, "Milk Salg") check("RN ukendt køretøj", names.v9, nil) end print(failures == 0 and "\nALLE TESTS OK" or ("\n" .. failures .. " FEJL")) os.exit(failures == 0 and 0 or 1)