Borderoul tine per factura ce mai are de completat si daca are articole de gestiune (camp gest, coloana si filtru), coada contabilizeaza in serie facturile bifate si incheie cu un rezumat, iar contul de furnizor/client se alege din planul de conturi. Cheia normalizata de articol, rezolvarea automata a partenerului si anularea in bloc intra tot aici. Pozitia in lista se pastreaza peste reaplicarea filtrului. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Aroafxp4z8bmM5oVECZRXY
181 lines
13 KiB
Markdown
181 lines
13 KiB
Markdown
# 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 - 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); `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 - vezi sectiunea 7 pentru simptomul complet de `.FXP`/`.ERR` stale.
|
|
- 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 <cColWithLabel> 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):
|
|
```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.
|
|
- **`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 timp
|
|
`INKEY(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.
|
|
|
|
## 7. Capcane la SCRIEREA scriptului de test
|
|
|
|
- **`SET SAFETY OFF` in primele linii, INAINTE de orice `STRTOFILE`/`CREATE CURSOR`/`COMPILE`**
|
|
(plus `SET TALK OFF`). Sub `-A` SAFETY porneste ON: primul `STRTOFILE` peste un log existent, sau
|
|
`COMPILE` peste un `.err`/`.fxp` existent, scoate dialogul modal "overwrite?" care blocheaza
|
|
procesul pana la timeout (proces viu, pare agatat). Alternativa/completare: `SAFETY=OFF`+
|
|
`RESOURCE=OFF` intr-un config.fpw propriu pasat cu `-c`. Verificare: logheaza
|
|
`SET("SAFETY")`/`SET("RESOURCE")` la start. 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.
|
|
- **`.FXP`/`.ERR` ramase 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.exe` se 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>.FXP` si `<nume>.ERR` inainte 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.** `.Objects` la un container
|
|
(`Form`, `Container`, `Grid`) e *Objects Array*: `TYPE('loO.Objects')` da `'A'`, nu `'O'`. O
|
|
garda `IF TYPE('loO.Objects') = 'O'` e deci mereu falsa, iar `AND`-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 la `TYPE('loO.Value')`
|
|
(tipul variaza cu continutul) si `TYPE('loO.Visible')`.
|
|
- **Optiunile citite ca globali (`gn<NUME>`/`gc<NUME>`) nu se reimprospateaza din `scrie_optiune()`.**
|
|
`scrie_optiune()` + `actualizeaza_optiuni()` scriu tabela `OPTIUNI` si cache-ul `crsOptiuni` (de unde
|
|
citeste `citeste_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 prin `scrie_optiune()`.
|