15 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 schemeiROMFASTSOFT; 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 faceCREATEOBJECT('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() }
-Aignora config.fpw implicit,-Tsare peste splash; ambele obligatorii - fara-Tprocesul 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
-Aporneste ON - vezi sectiunea 7 pentru capcana completa si remediu. - 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);QUITobligatoriu la final. LINENO()din handler e relativ la inceputul metodei: linia in.vc2= linia raportata + liniaPROCEDURE.- Nu rula cu IDE-ul sau exe-ul aplicatiei deschis (lock pe binare). Sterge
.fxp-ul vechi dupa editarea scriptului - vezi sectiunea 7 pentru simptomul complet de.FXP/.ERRstale. - Forme modale:
Show(1)blocheaza. Replica in script doar liniile relevante din Show, sau seteazaWindowType = 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",ENDWITHda 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 (
COMPILEscrie.fxplanga.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 deCOMPILE; verdictul = existenta lui<nume>.errdupa (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:
git show <rev>:clase\fisier.vcx > temp\fisier.vcx(+.vct); pentru diff lizibil,vcx2txt.ps1 -Source temp\fisier.vcx -CacheRoot temp\txt.- 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. - Fara git, din backup-urile de runda: copiaza
*.vc2.pre_rundaN.bakintr-un temp si regenereaza binarul cuFoxBin2Prg.EXE "<temp>\fisier.vc2" "" "" "" 1 0 1(pune..\include\foxpro.hlanga, altfel.ERR). - 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"). - 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 CURSORNU mosteneste indecsii sursei. OriceSeek(...,'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 (
ColumnCountdevine 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:attachtogridcopiaza 0 coloane, apoicalctotalda "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" laCREATEOBJECT-ul formei (faza de constructie, raportata la linia apelanta, Details gol). -
Erorile de instantiere se raporteaza la linia
CREATEOBJECT, nu la.Init: dacaProcedure= scriptul apelant, eroarea vine din evaluarea definitiei clasei (ADD OBJECT ... WITH RowSource, ControlSource-expresie) - bind-ul controalelor native se rezolva la CONSTRUCTIE, inainte deInit. -
Literal string > 255 caractere = eroare de COMPILARE, nu runtime (255 trece, 256 pica). Mesaj:
Unrecognized command verbdaca linia incepe cu un apel de metoda,Command contains unrecognized phrase/keywordla o atribuire. Insidios: restul programului ruleaza, doar linia lipseste din.fxpsi da eroare 16 cand se ajunge la ea. De aceea SQL-ul lung se scrie cuTEXT TO <var> [TEXTMERGE] NOSHOW ... ENDTEXTsau 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
.vc2poate 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)=1sau 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,Replacepe randul curent), unde pointerul e garantat pozitionat. -
SET PROCEDURE TO x.prg: in.exese rezolva din modulele compilate (orice.prgdin.pjx), din IDE doar prinSET PATH. O cale lipsa dinSET PATHse vede deci doar necompilat, iar inTrytrece tacut. -
INKEY(0, ...)nu e un poll, e asteptare NELIMITATA a unei taste. Pentru "a apasat utilizatorul ESC?" fara sa blochezi, foloseste timeout mic (INKEY(0.01, 'H')). Cat timpINKEY(0)asteapta, timerele VFP nu se declanseaza, deci un dialog/o bucla blocata asa nu poate fi deblocata din interiorul procesului - in rulare headless arata ca un proces viu care nu mai scrie in log. -
Doua constructii care NU compileaza, dar trec de
txt2vcx(storno eFactura, 18.09.2026):NEWOBJECT('c', 'lib.vcx', , p1, ...)cu argumentul 3 omis ("Function argument value, type, or count is invalid" - pune'') siCREATEOBJECT(...).Metoda()inlantuit ("Syntax error").txt2vcx(si-DryRun) NU compileaza metodele: dupa write-back, copiaza.vcx/.vctin ACELASI folder sub alt nume (parintii se rezolva relativ),COMPILE CLASSLIBheadless, citeste.ERR, sterge copia. -
FOR EACHpeloDom.selectNodes(...)da la rulare "Expression evaluator failed". FolosesteloN = loDom.selectNodes(...)+FOR i = 0 TO loN.length - 1/loN.item(i). -
COUNT ... TO x IN aliasnu e sintaxa VFP9 (eroare "Command contains unrecognized phrase/keyword", vizibila doar in.ERR):COUNTnu areIN; faSELECT (cAlias)inainte, apoiCOUNT FOR ... TO x.
7. Capcane la SCRIEREA scriptului de test
SET SAFETY OFFin primele linii, INAINTE de oriceSTRTOFILE/CREATE CURSOR/COMPILE(plusSET TALK OFF). Sub-ASAFETY porneste ON: primulSTRTOFILEpeste un log existent, sauCOMPILEpeste un.err/.fxpexistent, scoate dialogul modal "overwrite?" care blocheaza procesul pana la timeout (proces viu, pare agatat). Alternativa/completare:SAFETY=OFF+RESOURCE=OFFintr-un config.fpw propriu pasat cu-c. Verificare: logheazaSET("SAFETY")/SET("RESOURCE")la start. Suitele existente il au deja - capcana apare la scripturile ad-hoc, scrise repede pentru o masuratoare.DEFINE CLASS ... ENDDEFINEnu poate sta la mijlocul programului principal. Toate liniile de DUPAENDDEFINEdau "Statement is not in a procedure" (vezi<script>.ERR); scriptul se opreste silentios, fara sa ajunga laQUIT- simptom: vfp9.exe pare agatat la nesfarsit. Pune oriceDEFINE CLASSauxiliar la FINALUL fisierului.- Dummy-uri cu metode:
CREATEOBJECT('Custom')+AddProperty()adauga doar PROPRIETATI. Daca clasa testata apeleazagoExecutor.oExecute(...), dummy-ul trebuie sa fieDEFINE CLASS dummyexecutor AS CustomcuPROCEDURE oExecutereala. - Diagnostic "script agatat", in ordine: (1) exista
<script>.ERRlanga.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 cuEnumWindows/GetWindowText/GetWindowThreadProcessId(Add-Type, user32) filtrat pe PID - daca vezi doar fereastra principala + "Command", nu e dialog, cauta alta cauza (tipic-Tlipsa). Confirma mereu cauza inainte de a schimba scriptul. .FXP/.ERRramase de la o compilare esuata agata reluarea, chiar dupa ce ai reparat sursa. Daca scriptul a esuat o data la compilare si il reiei sub ACELASI nume,vfp9.exese blocheaza la nesfarsit (CPU=0, Responding=True) inainte sa scrie ceva in log dupa prima linie - simptomul seamana cu un blocaj de retea si trimite diagnosticul pe piste false (firewall, timeout TCP). Sterge<nume>.FXPsi<nume>.ERRinainte de fiecare reluare, sau da fiecarei incercari un nume nou. Platit pe testarea VIES (23.08.2026): doua blocaje de 90s+; dupa stergerea celor doua fisiere, exact acelasi script a rulat in 1.19s. Completeaza punctul (1) din capcana precedenta: nu doar "exista.ERR?", ci si "e.ERR-ul de la rularea asta sau de la una anterioara?".TYPE()pe un obiect nu intoarce'O'pentru orice membru..Objectsla un container (Form,Container,Grid) e Objects Array:TYPE('loO.Objects')da'A', nu'O'. O gardaIF TYPE('loO.Objects') = 'O'e deci mereu falsa, iarAND-ul scurtcircuiteaza testul corect de dupa ea - recursia pe arborele de controale nu porneste niciodata, fara eroare si fara nimic in log; singurul simptom e un dump care contine doar nivelul 1. Testeaza pe ce folosesti efectiv:TYPE('loO.Objects.Count') = 'N'e'U'daca lantul punctat nu exista, deci e si suficient, si independent de litera de tip. Aceeasi prudenta laTYPE('loO.Value')(tipul variaza cu continutul) siTYPE('loO.Visible').- Optiunile citite ca globali (
gn<NUME>/gc<NUME>) nu se reimprospateaza dinscrie_optiune().scrie_optiune()+actualizeaza_optiuni()scriu tabelaOPTIUNIsi cache-ulcrsOptiuni(de unde citesteciteste_optiune()), dar globalii sunt creati o singura data, la login/schimbare de firma (oinit_optiuni.prg,actualizeaza_optiuni_program/optiuni_firma). Un test care schimba o optiune si apoi apeleaza cod care citeste globalul masoara valoarea veche, fara niciun semn de eroare. Verifica intai pe ce cale citeste codul testat; daca e globalul, seteaza-l direct in test (si restaureaza-l la final), nu prinscrie_optiune(). - Liniile de dinainte de
ON ERRORdau dialog nativ pe ecranul utilizatorului.ALLTRIM(NVL(tcParam, ''))pe un parametru nepasat (.F.) = "Function argument value..." ca dialog "Program Error", nu in log. SeteazaON ERRORpe primele linii si folosesteTRANSFORM(tcParam)pentru parametri optionali. - Eroarea SQL prin
goExecutore o forma modala INTERNA VFP:watchdog_vfp.ps1nu o vede (nu e fereastra top-level), procesul sta pana la timeout. Verifica fiecareSELECTdin test cusqlplusinainte de rulare (ex.anaf_efacturanu arenumar_act/data_act- sunt in view-urileanaf_vefactura_*). - Forma din
.vcxcuCREATEOBJECTdirect poate pica inInitcu erori de binding ("Error with _checkbox1 - Value : Data type mismatch", CREATEOBJECT intoarce.F.), desi in aplicatie merge. Fa un stub.scxbazat pe clasa si deschide-l cuDO FORM ... NAME lo NOSHOW- asa se instantiaza ca in aplicatie, iar erorile ar arata calea completa a controlului. - Rularile de test se fac DOAR prin
watchdog_vfp.ps1 -AutoDismiss: un lansator propriu fara plasa a lasat 3 dialoguri modale pe ecranul utilizatorului (18.09.2026).