# 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 -A -T "" `, 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` 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 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 ` => 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 `.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 :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 "\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 [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): ```powershell $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 `