<# .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_, 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 }