Preserve damaged backup evidence across later saves
This commit is contained in:
@@ -75,6 +75,18 @@ namespace MagnetismHeroes.Tests
|
||||
Assert.That(Directory.GetFiles(_directory, "*.damaged-*").Select(File.ReadAllText), Does.Contain("broken-primary"));
|
||||
Assert.That(File.ReadAllText(_path + ".bak"), Is.EqualTo("broken-backup"));
|
||||
}
|
||||
[Test] public void BothDamagedOriginalsSurviveTwoSubsequentSuccessfulWrites()
|
||||
{
|
||||
File.WriteAllText(_path, "distinct-broken-primary"); File.WriteAllText(_path + ".bak", "distinct-broken-backup");
|
||||
var store = Store(); Assert.That(store.TryLoad(out _, out _), Is.False);
|
||||
Assert.That(store.TrySave(new ProgressSave(), out var error), Is.True, error);
|
||||
Assert.That(store.TrySave(Puzzle(), out error), Is.True, error);
|
||||
var forensic = Directory.GetFiles(_directory, "*.damaged-*").Select(File.ReadAllText).ToArray();
|
||||
Assert.That(forensic, Does.Contain("distinct-broken-primary"));
|
||||
Assert.That(forensic, Does.Contain("distinct-broken-backup"));
|
||||
Assert.That(store.TryLoad(out var restored, out error), Is.True, error);
|
||||
Assert.That(restored.puzzleStates.Single().stageId, Is.EqualTo(1));
|
||||
}
|
||||
[Test] public void InvalidGroupMembershipDoesNotReplaceAValidSave()
|
||||
{
|
||||
var store = Store(); var progress = Puzzle(); Assert.That(store.TrySave(progress, out _), Is.True);
|
||||
|
||||
@@ -14,6 +14,7 @@ namespace MagnetismHeroes.UnityRuntime.Persistence
|
||||
private readonly string _path;
|
||||
private readonly IReadOnlyList<StageDefinition> _definitions;
|
||||
private bool _preserveDamagedOriginal;
|
||||
private bool _preserveDamagedBackup;
|
||||
private sealed class SaveFieldsResolver : DefaultContractResolver
|
||||
{
|
||||
protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization serialization)
|
||||
@@ -32,6 +33,7 @@ namespace MagnetismHeroes.UnityRuntime.Persistence
|
||||
if (Read(_path + ".bak", out save, out var backupError))
|
||||
{ error = "Recovered valid backup: " + originalError; _preserveDamagedOriginal = true; return true; }
|
||||
_preserveDamagedOriginal = true;
|
||||
_preserveDamagedBackup = File.Exists(_path + ".bak");
|
||||
error = "Invalid primary and backup: " + originalError + "; " + backupError;
|
||||
return false;
|
||||
}
|
||||
@@ -105,6 +107,11 @@ namespace MagnetismHeroes.UnityRuntime.Persistence
|
||||
File.Copy(_path, _path + ".damaged-" + Guid.NewGuid().ToString("N"));
|
||||
_preserveDamagedOriginal = false;
|
||||
}
|
||||
if (_preserveDamagedBackup && File.Exists(_path + ".bak"))
|
||||
{
|
||||
File.Copy(_path + ".bak", _path + ".bak.damaged-" + Guid.NewGuid().ToString("N"));
|
||||
_preserveDamagedBackup = false;
|
||||
}
|
||||
using (var stream = new FileStream(temporary, FileMode.Create, FileAccess.Write, FileShare.None))
|
||||
using (var writer = new StreamWriter(stream))
|
||||
{ createdTemporary = true; writer.Write(JsonConvert.SerializeObject(save, Formatting.Indented, SerializerSettings)); writer.Flush(); stream.Flush(true); }
|
||||
|
||||
+1
-1
@@ -8,7 +8,7 @@ JSON v1은 앱 전용 `progress.json`에 확정 상태만 기록한다. 각 단
|
||||
|
||||
확정 이동·합체·분리·계수·공급·반납, 카메라 조작 종료, 화면 이동, 일시정지에서 저장한다. 미확정 드래그와 선택·접촉 표시를 저장하지 않는다. 저장 실패는 현재 화면에 짧은 한국어 안내를 보여주고 실제 파일 오류를 개발 로그에 남긴다. 다음 저장 성공 시 안내를 비운다. 복원은 Home에서 시작하며 이전 계수나 성공 음성을 반복하지 않는다. 같은 보드의 자유놀이 조작 중에는 마지막 숫자 대기열을 보존하고 화면 이동·일시정지는 음성을 정리한다.
|
||||
|
||||
임시 파일을 디스크에 flush한 뒤 primary를 교체한다. 유효 primary를 backup으로 보존하며 불완전 `.tmp`는 읽지 않는다. 손상 primary는 유효 backup 전체로 복구한다. 다음 저장 전에 손상 원본을 `.damaged-*`로 복사하며, 손상 primary를 유효 backup 위에 덮어쓰지 않는다. 둘 다 손상되면 정상 초기 상태와 안내를 표시하고 원본 증거를 보존한다.
|
||||
임시 파일을 디스크에 flush한 뒤 primary를 교체한다. 유효 primary를 backup으로 보존하며 불완전 `.tmp`는 읽지 않는다. 손상 primary는 유효 backup 전체로 복구한다. 다음 저장 전에 손상 원본을 `.damaged-*`로 복사하며, 손상 primary를 유효 backup 위에 덮어쓰지 않는다. 둘 다 손상되면 정상 초기 상태와 안내를 표시하고 primary·backup의 서로 다른 원본을 각각 보관해 이후 정상 저장에서도 증거를 보존한다.
|
||||
|
||||
검증 명령:
|
||||
|
||||
|
||||
@@ -60,6 +60,8 @@ try {
|
||||
Check 'clean-source' (!$result.sourceDirty) $result.sourceCommit
|
||||
$build=Get-Content -LiteralPath (Join-Path $EvidenceDir 'androidbuild-evidence.json') -Raw | ConvertFrom-Json
|
||||
Check 'build-provenance' ($build.status -eq 'passed' -and $build.exitCode -eq 0 -and !$build.sourceDirty -and !$build.sourceDirtyEnd -and $build.sourceCommit -eq $result.sourceCommit -and $build.sourceCommitEnd -eq $result.sourceCommit -and $build.apkSha256 -eq $result.apkSha256) $result.apkSha256
|
||||
Adb @('wait-for-device') | Out-Null
|
||||
Check 'device-boot-completed' ((Adb @('shell','getprop','sys.boot_completed')) -eq '1') 'Actual emulator boot completed before install'
|
||||
Adb @('install','-r',$ApkPath) | Out-Null; Adb @('logcat','-c') | Out-Null; Adb @('shell','pm','clear',$package) | Out-Null
|
||||
$result.device=@{model=(Adb @('shell','getprop','ro.product.model'));api=(Adb @('shell','getprop','ro.build.version.sdk'));abi=(Adb @('shell','getprop','ro.product.cpu.abilist'));bridge=(Adb @('shell','getprop','ro.dalvik.vm.native.bridge'))}
|
||||
Step 'initial-home' @('shell','am','start','-W','-n',$activity) 6500
|
||||
@@ -171,6 +173,8 @@ try {
|
||||
Step 'both-damaged-new-selection' @('shell','input','tap','1200','616'); $initial=Save 'both-damaged-new-initial'
|
||||
Check 'both-damage-normal-initial' ($initial.completedStageIds.Count -eq 0 -and $initial.puzzleStates.Count -eq 0 -and $null -eq $initial.latestFreePlay) 'No valid save starts clean initial state'
|
||||
Check 'both-damage-forensic-preserved' ((Adb @('shell','cat',($remote+'.damaged-*'))) -match 'damaged-both-primary' -and (Adb @('shell','cat',($remote+'.bak'))) -eq 'damaged-both-backup') 'Primary forensic and damaged backup retained'
|
||||
Step 'both-damage-second-normal-save' @('shell','input','tap','1200','562'); $secondInitial=Save 'both-damaged-second-initial'
|
||||
Check 'both-damage-two-normal-saves-forensic-preserved' ((Adb @('shell','cat',($remote+'.damaged-*'))) -match 'damaged-both-primary' -and (Adb @('shell','cat',($remote+'.bak.damaged-*'))) -match 'damaged-both-backup' -and $secondInitial.puzzleStates.Count -eq 1) 'Both distinct damaged byte strings survive two successful normal writes'
|
||||
$result.sourceCommitEnd=(& git rev-parse HEAD); $result.sourceDirtyEnd=[bool](& git status --porcelain)
|
||||
Check 'clean-source-end' (!$result.sourceDirtyEnd -and $result.sourceCommitEnd -eq $result.sourceCommit) $result.sourceCommitEnd
|
||||
$result.status='passed'
|
||||
|
||||
Reference in New Issue
Block a user