- git_sync.ps1 nou: conversie recursiva bin->text in arbore (staging temp, incremental, orfani, RoundtripExempt, DbfList cu date, anti-dialog cfg) - vfp_filemap.ps1: mapari .pjx->.pj2 si .dbf->.db2 - txt2vcx.ps1: CacheRoot implicit = ProjectRoot (text in-arbore) + cfg anti-dialog in staging - test_roundtrip.ps1: parametrizat per proiect, restaurare din backup temp (nu git checkout) - test_git_sync.ps1 nou: incremental, orfani, esec partial, protectie binar, scutire roundtrip - CLAUDE.md: fluxul nou documentat Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
224 lines
12 KiB
PowerShell
224 lines
12 KiB
PowerShell
<#
|
|
.SYNOPSIS
|
|
Teste standalone (fara Pester) pentru git_sync.ps1, pe fixture-uri construite in TEMP.
|
|
Acopera cele 4 comportamente noi cerute de plan (Sectiunea 8, decizie eng-review D2):
|
|
(A) incremental - a doua rulare pe fixture neschimbat nu reconverteste nimic (Convertite=0, Sarite>0);
|
|
(B) curatare orfani - un .vc2 fara binar se sterge si se raporteaza; un .db2 a carui
|
|
tabela a iesit din -DbfList se sterge si se raporteaza;
|
|
(C) esec partial - un .vcx corupt produce exit nenul si apare in esecuri, DAR fisierele
|
|
valide din aceeasi rulare se convertesc totusi;
|
|
(D) protectia binarului - dupa orice rulare, toate binarele fixture-ului sunt byte-identice
|
|
(SHA256 inainte/dupa). Verificat si dupa fiecare rulare din A/B/C.
|
|
|
|
Fixture: copie a celui mai mic .vcx+.vct real din Clase si a setului Locale (dbf+memo+indecsi+
|
|
container dbc) din proiectul sursa. NU se scrie nimic in proiectul sursa - doar se citeste/copiaza.
|
|
Se copiaza DOAR binarele si memo-urile, niciodata textul .??2 deja prezent in sursa.
|
|
|
|
.PARAMETER SourceProject
|
|
Proiectul din care se copiaza esantioanele de binare (implicit ROACONT).
|
|
#>
|
|
[CmdletBinding()]
|
|
param(
|
|
[string]$SourceProject = 'D:\ROA\ROACONT'
|
|
)
|
|
|
|
$ErrorActionPreference = 'Stop'
|
|
$script:GitSync = 'D:\ROA\UTIL\foxbin2prg\git_sync.ps1'
|
|
$script:Failures = @()
|
|
# extensiile considerate "binar" in fixture (tot ce NU e text .??2 generat)
|
|
$script:BinExts = @('.vcx','.vct','.dbf','.fpt','.cdx','.dbc','.dct','.dcx')
|
|
|
|
function Assert([bool]$cond, [string]$msg) {
|
|
if ($cond) { Write-Host " PASS: $msg" -ForegroundColor Green }
|
|
else { Write-Host " FAIL: $msg" -ForegroundColor Red; $script:Failures += $msg }
|
|
}
|
|
|
|
# --- Ruleaza git_sync.ps1 ca proces copil si parseaza sumarul + orfanii + exit code ---
|
|
function Invoke-GitSync {
|
|
param([string]$Fixture, [string[]]$DbfList, [string[]]$Exempt, [int]$TimeoutSeconds = 30)
|
|
# -Command (nu -File): powershell.exe -File colapseaza 'a','b' intr-un singur token, DbfList ar fi gol
|
|
$dbfLit = if ($DbfList -and $DbfList.Count) { '@(' + (($DbfList | ForEach-Object { "'" + $_ + "'" }) -join ',') + ')' } else { '@()' }
|
|
$exLit = if ($Exempt -and $Exempt.Count) { '@(' + (($Exempt | ForEach-Object { "'" + $_ + "'" }) -join ',') + ')' } else { '@()' }
|
|
$cmd = "& '$script:GitSync' -ProjectRoot '$Fixture' -DbfList $dbfLit -RoundtripExempt $exLit -TimeoutSeconds $TimeoutSeconds"
|
|
# EAP=Continue local: git_sync ruleaza FoxBin2Prg care scrie pe stderr (ex. 'not a table');
|
|
# sub EAP=Stop, capturarea 2>&1 a acelui stderr ar arunca NativeCommandError si ar opri testul.
|
|
$prevEAP = $ErrorActionPreference
|
|
$ErrorActionPreference = 'Continue'
|
|
$sw = [Diagnostics.Stopwatch]::StartNew()
|
|
$raw = & powershell -ExecutionPolicy Bypass -Command $cmd 2>&1 | Out-String
|
|
$exit = $LASTEXITCODE
|
|
$sw.Stop()
|
|
$ErrorActionPreference = $prevEAP
|
|
$conv = $null; $skip = $null; $fail = $null
|
|
$m = [regex]::Match($raw, 'Convertite:\s*(\d+)\s+Sarite\(la zi\):\s*(\d+)\s+Esuate:\s*(\d+)')
|
|
if ($m.Success) { $conv = [int]$m.Groups[1].Value; $skip = [int]$m.Groups[2].Value; $fail = [int]$m.Groups[3].Value }
|
|
$orphans = @()
|
|
foreach ($line in ($raw -split "`r?`n")) {
|
|
$om = [regex]::Match($line, '^\s*orfan:\s*(.+?)\s*$')
|
|
if ($om.Success) { $orphans += $om.Groups[1].Value }
|
|
}
|
|
return [PSCustomObject]@{ Exit=$exit; Convertite=$conv; Sarite=$skip; Esuate=$fail; Orphans=$orphans; Raw=$raw; ElapsedSec=[math]::Round($sw.Elapsed.TotalSeconds,1); TimeoutSec=$TimeoutSeconds }
|
|
}
|
|
|
|
# --- Snapshot SHA256 al tuturor binarelor din fixture (pt protectia binarului) ---
|
|
function Get-BinSnapshot([string]$root) {
|
|
$h = @{}
|
|
Get-ChildItem -LiteralPath $root -Recurse -File -ErrorAction SilentlyContinue |
|
|
Where-Object { $script:BinExts -contains $_.Extension.ToLower() } |
|
|
ForEach-Object { $h[$_.FullName.ToLower()] = (Get-FileHash -LiteralPath $_.FullName -Algorithm SHA256).Hash }
|
|
return $h
|
|
}
|
|
|
|
function Assert-BinariesUnchanged([hashtable]$before, [string]$root, [string]$ctx) {
|
|
$after = Get-BinSnapshot $root
|
|
$why = ''
|
|
if ($before.Count -ne $after.Count) { $why = "numar binare $($before.Count)->$($after.Count)" }
|
|
else {
|
|
foreach ($k in $before.Keys) {
|
|
if (-not $after.ContainsKey($k)) { $why = "lipsa dupa rulare: $k"; break }
|
|
if ($after[$k] -ne $before[$k]) { $why = "modificat: $k"; break }
|
|
}
|
|
}
|
|
Assert ($why -eq '') "${ctx}: toate binarele byte-identice dupa rulare$(if($why){" ($why)"})"
|
|
}
|
|
|
|
# --- Construieste un fixture in TEMP: atentie.vcx+.vct + setul Locale (fara text .??2) ---
|
|
function New-Fixture([string]$tag) {
|
|
$fx = Join-Path $env:TEMP ("gs_test_{0}_{1}_{2}" -f $tag, $PID, ([Guid]::NewGuid().ToString('N').Substring(0,8)))
|
|
New-Item -ItemType Directory -Force -Path (Join-Path $fx 'Clase'), (Join-Path $fx 'Locale') | Out-Null
|
|
# doar .vcx + .vct (memo); NU .vc2 care exista deja in sursa dintr-o rulare anterioara
|
|
Get-ChildItem (Join-Path $SourceProject 'Clase') -Filter 'atentie.*' -File |
|
|
Where-Object { @('.vcx','.vct') -contains $_.Extension.ToLower() } |
|
|
ForEach-Object { Copy-Item -LiteralPath $_.FullName -Destination (Join-Path $fx 'Clase') -Force }
|
|
# set Locale: doua tabele (pt scenariul de scoatere din lista) + indecsi + container dbc
|
|
foreach ($f in 'locale.dbf','locale.FPT','locale.CDX','locale.dbc','locale.DCT','locale.DCX','locale_lang.dbf','locale_lang.FPT','locale_lang.cdx') {
|
|
$s = Join-Path $SourceProject "Locale\$f"
|
|
if (Test-Path -LiteralPath $s) { Copy-Item -LiteralPath $s -Destination (Join-Path $fx 'Locale') -Force }
|
|
}
|
|
return $fx
|
|
}
|
|
|
|
# Sterge fixture-ul + folderul-parinte de staging lasat de git_sync (TEMP\git_sync_<leaf>, ramane gol)
|
|
function Remove-Fixture([string]$fx) {
|
|
Remove-Item -Recurse -Force -LiteralPath $fx -ErrorAction SilentlyContinue
|
|
$stagingParent = Join-Path $env:TEMP ("git_sync_" + (Split-Path $fx -Leaf))
|
|
Remove-Item -Recurse -Force -LiteralPath $stagingParent -ErrorAction SilentlyContinue
|
|
}
|
|
|
|
$script:BothDbf = @('Locale\locale.dbf','Locale\locale_lang.dbf')
|
|
|
|
# ============================== SCENARIILE ==============================
|
|
|
|
function Test-Incremental {
|
|
Write-Host "`n--- (A) incremental: a doua rulare nu reconverteste nimic ---"
|
|
$fx = New-Fixture 'inc'
|
|
try {
|
|
$snap = Get-BinSnapshot $fx
|
|
$r1 = Invoke-GitSync -Fixture $fx -DbfList $script:BothDbf
|
|
Assert ($r1.Exit -eq 0) "A: prima rulare exit 0 (exit=$($r1.Exit))"
|
|
Assert ($r1.Convertite -gt 0) "A: prima rulare converteste (Convertite=$($r1.Convertite) > 0)"
|
|
Assert-BinariesUnchanged $snap $fx "A(run1)"
|
|
|
|
$r2 = Invoke-GitSync -Fixture $fx -DbfList $script:BothDbf
|
|
Assert ($r2.Exit -eq 0) "A: a doua rulare exit 0 (exit=$($r2.Exit))"
|
|
Assert ($r2.Convertite -eq 0) "A: a doua rulare NU reconverteste (Convertite=$($r2.Convertite) = 0)"
|
|
Assert ($r2.Sarite -gt 0) "A: a doua rulare sare fisierele la zi (Sarite=$($r2.Sarite) > 0)"
|
|
Assert-BinariesUnchanged $snap $fx "A(run2)"
|
|
} finally { Remove-Fixture $fx }
|
|
}
|
|
|
|
function Test-Orphans {
|
|
Write-Host "`n--- (B) curatare orfani: .vc2 fara binar + .db2 iesit din lista ---"
|
|
$fx = New-Fixture 'orf'
|
|
try {
|
|
# orfan de tip 1: text .vc2 fara binar .vcx corespondent
|
|
$orfVc2 = Join-Path $fx 'Clase\_orfan.vc2'
|
|
Set-Content -LiteralPath $orfVc2 -Value '* text orfan fara binar corespondent' -Encoding Ascii
|
|
$snap = Get-BinSnapshot $fx
|
|
|
|
# run1: ambele tabele in lista -> produce db2-uri; orfanul fara binar se sterge
|
|
$r1 = Invoke-GitSync -Fixture $fx -DbfList $script:BothDbf
|
|
Assert (-not (Test-Path -LiteralPath $orfVc2)) "B: .vc2 orfan (fara binar) sters"
|
|
Assert (($r1.Orphans -join ';') -match '(?i)_orfan\.vc2') "B: .vc2 orfan raportat in sumar"
|
|
Assert (Test-Path -LiteralPath (Join-Path $fx 'Locale\locale_lang.db2')) "B: locale_lang.db2 creat in run1"
|
|
Assert-BinariesUnchanged $snap $fx "B(run1)"
|
|
|
|
# run2: locale_lang scos din lista -> db2-ul lui iese din lista -> orfan
|
|
$r2 = Invoke-GitSync -Fixture $fx -DbfList @('Locale\locale.dbf')
|
|
Assert (-not (Test-Path -LiteralPath (Join-Path $fx 'Locale\locale_lang.db2'))) "B: db2 al tabelei iesite din lista sters"
|
|
Assert (($r2.Orphans -join ';') -match '(?i)locale_lang\.db2') "B: db2 iesit din lista raportat in sumar"
|
|
Assert (Test-Path -LiteralPath (Join-Path $fx 'Locale\locale.db2')) "B: db2 al tabelei ramase in lista pastrat"
|
|
Assert-BinariesUnchanged $snap $fx "B(run2)"
|
|
} finally { Remove-Fixture $fx }
|
|
}
|
|
|
|
function Test-PartialFailure {
|
|
Write-Host "`n--- (C) esec partial: binar corupt esueaza, restul se convertesc ---"
|
|
$fx = New-Fixture 'fail'
|
|
try {
|
|
# binar corupt: octeti garbage in .vcx si .vct (FoxBin2Prg: 'not a table', ErrorLevel != 0)
|
|
$g1 = New-Object byte[] 800; (New-Object Random).NextBytes($g1)
|
|
[IO.File]::WriteAllBytes((Join-Path $fx 'Clase\corupt.vcx'), $g1)
|
|
$g2 = New-Object byte[] 600; (New-Object Random).NextBytes($g2)
|
|
[IO.File]::WriteAllBytes((Join-Path $fx 'Clase\corupt.vct'), $g2)
|
|
$snap = Get-BinSnapshot $fx
|
|
|
|
$r = Invoke-GitSync -Fixture $fx -DbfList @('Locale\locale.dbf')
|
|
Assert ($r.Exit -ne 0) "C: exit nenul la esec partial (exit=$($r.Exit))"
|
|
Assert ($r.Esuate -ge 1) "C: cel putin un esec raportat (Esuate=$($r.Esuate))"
|
|
Assert ($r.Raw -match '(?i)corupt\.vcx') "C: fisierul corupt apare in lista de esecuri"
|
|
Assert ($r.Convertite -ge 1) "C: fisierele valide se convertesc totusi (Convertite=$($r.Convertite))"
|
|
Assert (Test-Path -LiteralPath (Join-Path $fx 'Clase\atentie.vc2')) "C: text valid (atentie.vc2) produs in ciuda esecului"
|
|
# esecul trebuie sa fie rapid (ErrorLevel, nu dialog modal): sub watchdog inseamna ca nu a aparut MessageBox VFP
|
|
Assert ($r.ElapsedSec -lt $r.TimeoutSec) "C: esec rapid fara dialog modal ($($r.ElapsedSec)s < watchdog $($r.TimeoutSec)s)"
|
|
Assert-BinariesUnchanged $snap $fx "C"
|
|
} finally { Remove-Fixture $fx }
|
|
}
|
|
|
|
function Test-BinaryProtection {
|
|
Write-Host "`n--- (D) protectia binarului: byte-identic dupa conversie completa ---"
|
|
$fx = New-Fixture 'bin'
|
|
try {
|
|
$snap = Get-BinSnapshot $fx
|
|
Assert ($snap.Count -gt 0) "D: fixture-ul contine binare de protejat ($($snap.Count))"
|
|
$r = Invoke-GitSync -Fixture $fx -DbfList $script:BothDbf
|
|
Assert ($r.Exit -eq 0) "D: rulare curata (exit=$($r.Exit))"
|
|
Assert-BinariesUnchanged $snap $fx "D"
|
|
} finally { Remove-Fixture $fx }
|
|
}
|
|
|
|
function Test-Exempt {
|
|
Write-Host "`n--- (E) scutire roundtrip: fisierul din -RoundtripExempt produce text fara garda roundtrip ---"
|
|
$fx = New-Fixture 'exempt'
|
|
try {
|
|
$snap = Get-BinSnapshot $fx
|
|
$r = Invoke-GitSync -Fixture $fx -DbfList @('Locale\locale.dbf') -Exempt @('Clase\atentie.vcx')
|
|
Assert ($r.Exit -eq 0) "E: rulare curata cu scutire (exit=$($r.Exit))"
|
|
Assert ($r.Raw -match '(?i)atentie\.vcx \(roundtrip scutit\)') "E: fisierul scutit raportat 'roundtrip scutit'"
|
|
Assert (Test-Path -LiteralPath (Join-Path $fx 'Clase\atentie.vc2')) "E: text produs pentru fisierul scutit"
|
|
Assert-BinariesUnchanged $snap $fx "E"
|
|
} finally { Remove-Fixture $fx }
|
|
}
|
|
|
|
# ============================== MAIN ==============================
|
|
if (-not (Test-Path -LiteralPath $script:GitSync)) { throw "Nu gasesc git_sync.ps1: $script:GitSync" }
|
|
if (-not (Test-Path -LiteralPath $SourceProject)) { throw "Proiect sursa inexistent: $SourceProject" }
|
|
|
|
Write-Host "=== test_git_sync.ps1 -- SourceProject=$SourceProject ==="
|
|
Test-Incremental
|
|
Test-Orphans
|
|
Test-PartialFailure
|
|
Test-BinaryProtection
|
|
Test-Exempt
|
|
|
|
Write-Host "`n=== REZULTAT ==="
|
|
if ($script:Failures.Count -eq 0) {
|
|
Write-Host "TOATE testele au trecut." -ForegroundColor Green
|
|
exit 0
|
|
} else {
|
|
Write-Host "$($script:Failures.Count) test(e) esuate:" -ForegroundColor Red
|
|
$script:Failures | ForEach-Object { Write-Host " - $_" -ForegroundColor Red }
|
|
exit 1
|
|
}
|
|
|