Files
foxbin2prg/test_git_sync.ps1
Marius Mutu a05687a38f Flux git-text in-arbore: git_sync.ps1 + teste, txt2vcx in-place, filemap pjx/dbf
- 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>
2026-07-16 11:04:23 +03:00

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
}