Files
comun/docs/depanare_testare_vfp.md
Marius Mutu c4d869921d verificare partener: garda pe cont NULL, hook-uri de disciplina, docs compactate
- ooperatii_comune: verific_partener nu mai construieste SQL NULL cand contul
  primit e NULL (EMPTY(.NULL.) e .F. in VFP)
- utile\context_watch.ps1 si utile\docs_revizie_check.ps1: masurarea contextului
  sesiunii si cadenta reviziei de documentatie, prin hook-uri Claude Code
  (instalare in docs\monitorizare-context.md)
- reguli_lucru: delegare la subagenti, modificari minime si scoped, scrierea si
  revizuirea documentatiei, changelog strictul necesar (regulile 3, 6, 9, 11, 12)
- scripturi-migrare-db: continutul unui script (scoped, fara select, idempotent)
- teste noi pentru cele doua erori din achizitia de import
- restul documentatiei compactata, fara pierdere de reguli

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RYbiinqXxdEqXi53x4Ro7K
2026-08-02 22:30:44 +03:00

11 KiB

Depanare si testare VFP fara IDE (headless)

Metoda de depanare a bug-urilor runtime VFP, valabila pentru toate proiectele ROA. Complementara cu flux-editare-vfp-text.md (editare .vcx/.scx pe text), cautare_vcx_vct.md (cautare in binare) si testare-ui-vfp.md (teste cu formular vizibil + screenshots). Reguli de livrare: reguli_lucru.md.

1. Fisiere de mediu si exemple

  • COMUN\utile\Teste\test_init_env_auto.prg - initializare completa de mediu FARA interactiune (parametri: host, schema, parola; implicit CENTRAL / MARIUSM_AUTO, parola schemei ROMFASTSOFT; user aplicatie "MARIUS M" / "123"). Seteaza PATH/CLASSLIB/PROCEDURE ca programul principal si instantiaza goConn/goExecutor/goApp/goLog/goFirma/goCalendar + pachetele de sesiune Oracle. Dupa apel poti face CREATEOBJECT('frm_xxx') si apela metodele punctual. Varianta interactiva (INPUTBOX + meniu): COMUN\programe\test_init_env.prg.
  • COMUN\utile\Teste\test_repro_cnrcrt_footer.prg - harness model: init mediu -> replicare flux -> instantiere forma reala -> executie pas cu pas cu logare -> verdict OK/BUG.
  • COMUN\utile\Teste\test_repro_import_nota.prg - harness fara Oracle (cursori + goExecutor dummy) cu parametru care incarca o copie de clasa din scratchpad, pentru bisectie A/B.

2. Rulare headless

vfp9.exe -c<config.fpw> -A -T "<script.prg>" <param>, lansat din PowerShell cu timeout:

$p = Start-Process 'C:\Program Files (x86)\Microsoft Visual FoxPro 9\vfp9.exe' -ArgumentList '-A','-T','"D:\...\script.prg" PARAM' -PassThru
$p.WaitForExit(120000); if (!$p.HasExited) { $p.Kill() }
  • -A ignora config.fpw implicit, -T sare peste splash; ambele obligatorii - fara -T procesul ramane pe ecranul de intampinare (simptom identic cu o eroare de compilare). -c<cale> explicit (lipit de flag) e preluat si cu -A.
  • SAFETY: sub -A porneste ON - primul STRTOFILE peste un log existent (sau COMPILE peste un .err/.fxp existent) scoate dialogul modal "overwrite?" si procesul pare agatat. Pune SET SAFETY OFF ca PRIMA linie din script si/sau SAFETY=OFF+RESOURCE=OFF intr-un config.fpw propriu pasat cu -c. Verificare: logheaza SET("SAFETY")/SET("RESOURCE") la start.
  • In script: logare cu STRTOFILE (nu WAIT WINDOW / debugger - nu exista consola); ON ERROR DO <handler> WITH ERROR(), MESSAGE(), PROGRAM(), LINENO() care logheaza si continua (mimeaza ErrorHandler); QUIT obligatoriu la final.
  • LINENO() din handler e relativ la inceputul metodei: linia in .vc2 = linia raportata + linia PROCEDURE.
  • Nu rula cu IDE-ul sau exe-ul aplicatiei deschis (lock pe binare); sterge .fxp-ul vechi dupa editarea scriptului - altfel VFP ruleaza codul vechi din .fxp fara niciun semn (testul poate trece fals; se prinde doar cand lipseste din log o linie scrisa doar de codul nou).
  • Forme modale: Show(1) blocheaza. Replica in script doar liniile relevante din Show, sau seteaza WindowType = 0.

3. Citirea log-ului aplicatiei (log.txt din radacina proiectului)

ErrorHandler scrie nErrror, cMethod, line (0 daca exe-ul e fara debug info) si stack-ul. Semantica ON ERROR, esentiala la interpretare:

  • Dupa RETURN din handler executia CONTINUA de la linia urmatoare => o singura cauza produce CASCADE de erori. Tipar clasic: eroare pe WITH <expr> => liniile .prop= dau 1940 "Expression is not valid outside of WITH/ENDWITH", ENDWITH da 1939. Prima eroare din secventa e cea reala, restul e zgomot.
  • Erorile din TRY/CATCH nu ajung in log. Un CATCH gol poate ascunde ani un defect latent care "explodeaza" cand codul din jur se repara.
  • Liniile "ON..." din stack sunt dispatch-uri (ON ERROR / READ EVENTS), nu metode reale.

4. Verificare de sintaxa prin compilare headless (dupa orice editare de .prg)

Cel mai ieftin test dupa o modificare: COMPILE headless, fara aplicatie si fara Oracle.

  • Compileaza o COPIE a fisierului in scratchpad, nu sursa din working copy (COMPILE scrie .fxp langa .prg, care ar aparea ca modificare in SVN). VFP nu rezolva simboluri externe la compilare, deci copia izolata e suficienta pentru sintaxa.
  • Sterge .err-ul vechi INAINTE de COMPILE; verdictul = existenta lui <nume>.err dupa (exista => logheaza continutul; nu exista => compilare curata).
  • Nu inlocuieste testul functional: prinde sintaxa, nu apeluri catre metode inexistente.

5. Bisectia versiunilor de clase (regresii)

Cand un bug apare "de azi", compara cu clasa veche fara sa atingi SVN:

  1. git show <rev>:clase\fisier.vcx > temp\fisier.vcx (+ .vct); pentru diff lizibil, vcx2txt.ps1 -Source temp\fisier.vcx -CacheRoot temp\txt.
  2. In harness incarca DOAR copia (SET CLASSLIB TO temp\fisier.vcx), nu ADDITIVE peste cea reala - rezolvarea intre doua classlib-uri cu aceeasi clasa nu e garantata => fals negativ.
  3. Fara git, din backup-urile de runda: copiaza *.vc2.pre_rundaN.bak intr-un temp si regenereaza binarul cu FoxBin2Prg.EXE "<temp>\fisier.vc2" "" "" "" 1 0 1 (pune ..\include\foxpro.h langa, altfel .ERR).
  4. Bisectie in interiorul unei runde: neutralizeaza valori IN LOC (ControlSource -> camp simplu, sterge linii de proprietate) - NU sterge obiecte/coloane din ADD OBJECT (strica structura: "Cannot add this object to a Grid").
  5. Cand diferenta apare dupa un fix "inofensiv" in COMUN, cauza poate fi un defect latent activat de fix (cod care nu rulase niciodata incepe sa ruleze), nu diff-ul in sine.

6. Capcane VFP (de verificat la bug-uri asemanatoare)

  • SELECT ... INTO CURSOR NU mosteneste indecsii sursei. Orice Seek(...,'cursor','tag') pe copie crapa; recreeaza indexul dupa copiere. Acelasi lucru pentru cursorii creati intr-un test: trebuie sa aiba EXACT indecsii pe care ii creeaza fluxul real inainte de instantiere, altfel ordinea/logica difera si testul da rezultat fals.

  • Un grid isi pierde TOATE coloanele (ColumnCount devine 0, fara eroare in log) daca ControlSource crapa la re-evaluare cat timp coloanele sunt atinse programatic (ex. font per coloana la Init). Simptome in aval: attachtogrid copiaza 0 coloane, apoi calctotal da "Property is not found". Similar, un cursor legat la grid recreat de Init lasa grid-ul fara coloane (eroare 1925).

  • ControlSource-uri cu campuri inexistente sunt tolerate la afisare, dar orice expresie care ARUNCA eroare la evaluare (Seek pe tag lipsa, functie nedefinita) declanseaza cazul de mai sus. Repro/regresie: utile\Teste\test_repro_cnrcrt_footer.prg.

  • Coloana de grid cu ControlSource EXPRESIE (nu camp) cere Bound = .F. Fara el: eroare 9 "Data type mismatch" la CREATEOBJECT-ul formei (faza de constructie, raportata la linia apelanta, Details gol).

  • Erorile de instantiere se raporteaza la linia CREATEOBJECT, nu la .Init: daca Procedure = scriptul apelant, eroarea vine din evaluarea definitiei clasei (ADD OBJECT ... WITH RowSource, ControlSource-expresie) - bind-ul controalelor native se rezolva la CONSTRUCTIE, inainte de Init.

  • Literal string > 255 caractere = eroare de COMPILARE, nu runtime (255 trece, 256 pica). Mesaj: Unrecognized command verb daca linia incepe cu un apel de metoda, Command contains unrecognized phrase/keyword la o atribuire. Insidios: restul programului ruleaza, doar linia lipseste din .fxp si da eroare 16 cand se ajunge la ea. De aceea SQL-ul lung se scrie cu TEXT TO <var> [TEXTMERGE] NOSHOW ... ENDTEXT sau concatenat. Verificare: awk 'length($0) > 260 {print NR": "length($0)}' fisier.prg.

  • Linie de COD (nu doar literal string) prea lunga intr-o metoda de clasa .vc2 poate arunca eroarea 11 ("Function argument value, type, or count is invalid") pe PRIMA instructiune a metodei, nu pe linia vinovata - simptom derutant. Se exclude prin diagnostic: aceeasi expresie merge normal la nivel de program, in alta clasa, intr-o subclasa cu metoda noua, si pe instanta virgina - deci nu tine de tipul datelor, de context sau de clasa parinte. Reper practic: linii preexistente >200 caractere intr-o clasa mare functioneaza pana la ~250; pragul real e in jur de 255, ca la literalii de string. Remediu: sparge expresia in pasi cu variabile locale. Verificare (comparat intre fisierul curent si un backup anterior):

    $l=[IO.File]::ReadAllLines($p,[Text.Encoding]::GetEncoding(28591))
    for($i=0;$i -lt $l.Length;$i++){ if($l[$i].Length -gt 200){ "{0}: len={1}" -f ($i+1), $l[$i].Length } }
    
  • Un UDF care citeste campul curent (Nvl(camp,0)=1 sau similar) nu e de incredere intr-o clauza de filtrare (SELECT ... WHERE, LOCATE FOR, SCAN FOR, DELETE FOR) - pointerul nu e garantat pe randul evaluat la fiecare apel, deci filtrul devine practic o valoare constanta (nu se declanseaza deloc, sau loveste tot). Simptome vazute: comasare de randuri care nu se mai producea deloc; test agatat. Remediu: in clauze de filtrare foloseste expresia INLINE pe camp (Nvl(camp,0) <> 1); pastreaza helper-ul doar in cod procedural (If, Replace pe randul curent), unde pointerul e garantat pozitionat.

  • SET PROCEDURE TO x.prg: in .exe se rezolva din modulele compilate (orice .prg din .pjx), din IDE doar prin SET PATH. O cale lipsa din SET PATH se vede deci doar necompilat, iar in Try trece tacut.

7. Capcane la SCRIEREA scriptului de test

  • SET SAFETY OFF in primele linii, INAINTE de orice STRTOFILE/CREATE CURSOR (plus SET TALK OFF). Fara el VFP deschide dialogul de confirmare la suprascriere, iar headless dialogul blocheaza procesul pana la timeout. Suitele existente il au deja - capcana apare la scripturile ad-hoc, scrise repede pentru o masuratoare.
  • DEFINE CLASS ... ENDDEFINE nu poate sta la mijlocul programului principal. Toate liniile de DUPA ENDDEFINE dau "Statement is not in a procedure" (vezi <script>.ERR); scriptul se opreste silentios, fara sa ajunga la QUIT - simptom: vfp9.exe pare agatat la nesfarsit. Pune orice DEFINE CLASS auxiliar la FINALUL fisierului.
  • Dummy-uri cu metode: CREATEOBJECT('Custom') + AddProperty() adauga doar PROPRIETATI. Daca clasa testata apeleaza goExecutor.oExecute(...), dummy-ul trebuie sa fie DEFINE CLASS dummyexecutor AS Custom cu PROCEDURE oExecute reala.
  • Diagnostic "script agatat", in ordine: (1) exista <script>.ERR langa .prg? => s-a oprit la compilare; (2) CPU-ul procesului ~0 dupa cateva secunde (Get-Process | select CPU) => asteapta un dialog modal; confirma enumerand ferestrele procesului cu EnumWindows/GetWindowText/GetWindowThreadProcessId (Add-Type, user32) filtrat pe PID - daca vezi doar fereastra principala + "Command", nu e dialog, cauta alta cauza (tipic -T lipsa). Confirma mereu cauza inainte de a schimba scriptul.