Files
comun/docs/cercetare/rec_watchdog_vfp.md
Marius Mutu 6de489950c sync SVN r18016: editare articole in factura de vanzare, precizie pret achizitie
ROAFACTURARE 2.11.15 (r18015): programe/ofacturare_editare.prg, programe/ofacturare_stoc.prg.
docs: conventia de encoding .sc2/.vc2 corectata la cp1250; cercetarile trans-proiect mutate
din ROAFACTURARE in docs/cercetare (r18014); actualizari reguli_lucru, oracle_export,
flux-editare-vfp-text, scripturi-migrare-db, flux-modificare-stergere-nota-jurnal, todos.
.gitignore: watchdog_out si PNG-urile din rularile headless (r18008).
Text FoxBin2Prg regenerat: clase/comun.vc2, ferestre/frm_import_note_facturi_clienti.sc2,
ferestre/frm_initializare_facturi_balanta.sc2.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AV7dQbTLAqvrtQbH7zxXxw
2026-08-20 14:07:12 +03:00

13 KiB

Watchdog VFP + diagnostic blocaj "View Parameter" (test_page3_articole.prg)

1. Utilitarul: COMUN\utile\Teste\watchdog_vfp.ps1

Lanseaza vfp9.exe -A -T "<script>", polleaza la ~700ms ferestrele TOP-LEVEL ale procesului (EnumWindows+GetWindowThreadProcessId, filtrate pe PID). Fereastra principala VFP se identifica prin Process.MainWindowHandle (.NET) - nu dupa numele clasei: VFP inregistreaza clase diferite prefixate vfp9... atat pentru shell-ul principal (vfp99400000) cat si pentru dialogurile lui proprii (vfp994000002 pentru "View Parameter") - o clasificare pe clasa a lasat "View Parameter" nedetectat la prima incercare (corectat).

Pentru fiecare fereastra noua (diferita de MainWindowHandle): captura PNG (PrintWindow, fallback CopyFromScreen daca iese neagra) + dump text (titlu, clasa, si textul/clasa fiecarui control copil via WM_GETTEXT, cross-proces). Cu -AutoDismiss: cauta un buton copil "Cancel"/"Anulare" si trimite BM_CLICK; altfel incearca, in ordine, WM_COMMAND IDCANCEL -> ESCAPE ca MESAJ (WM_KEYDOWN/WM_KEYUP, tintit pe handle) -> WM_CLOSE.

REGULA OBLIGATORIE, incalcata initial si corectata: watchdog-ul NU are voie sa foloseasca INPUT REAL de tastatura/mouse (keybd_event, SetForegroundWindow, SendInput, mouse_event) - masina e PARTAJATA cu utilizatorul. Prima versiune folosea SetForegroundWindow+keybd_event(ESCAPE) ca fallback pentru dialogurile proprii VFP owner-drawn (fara controale copil reale) - acest input NU are tinta, ajunge in orice fereastra are focus pe masina in acel moment, indiferent al cui proces e. Fapt clarificat de team-lead, dupa investigare: in acest caz concret, ESC-ul a aterizat de fapt in PROPRIUL NOSTRU proces de test (eroarea "Variable 'GNAN' is not found" vazuta imediat inainte de "Execution was canceled by the user" e chiar semnatura testului nostru) - nu in sesiunea lui Marius, deci de fapt nu s-a intrerupt munca nimanui de data asta. Asta NU schimba regula: mecanismul tot nu are tinta si putea la fel de usor sa ajunga in aplicatia de productie (roafacturare.exe/ roacont.exe) daca utilizatorul avea focus acolo in acea clipa - o formulare anterioara aici spunea gresit ca ar fi afectat sesiunea utilizatorului, corectata acum. Scos complet din cod (nu mai exista nicio linie keybd_event/SetForegroundWindow in watchdog_vfp.ps1). Consecinta: pentru dialogurile owner-drawn care nu raspund la mesaje tintite, -AutoDismiss esueaza cinstit (dialogul ramane deschis pana la -TimeoutSec, apoi procesul e omorat) - captura (PNG+dump text) ramane de incredere, dismiss-ul nu e garantat pentru acest tip de dialog. Procesul e omorat mereu la iesire (finally), nu ramane niciodata viu.

Validat intai pe caz banal: COMUN\utile\Teste\watchdog_selftest.prg (MESSAGEBOX cu OK/Cancel) - detectie, captura, dump text si auto-dismiss confirmate corecte inainte de a-l rula pe cazul real.

Utilizare: powershell -ExecutionPolicy Bypass -File watchdog_vfp.ps1 -Script "<test.prg>" -AutoDismiss [-TimeoutSec 90] [-MaxDialogs 10] [-OutDir <cale>].

2. Dialogurile capturate pe test_page3_articole.prg

Dialog 0 - nativ VFP, clasa vfp994000002, titlu "View Parameter", text "Enter the value for gnAn:" (fara controale copil reale - owner-drawn). Screenshot: COMUN\utile\Teste\editare_factura\watchdog_out\test_page3_articole_dialog0.png.

Dialog 1 (apare doar cand dialog 0 e inchis prin ESCAPE real, care functioneaza) - "Open" Win32 standard, filtru "Table/DBF (*.dbf)", folder implicit ROACONT (working directory-ul mediului de test, mostenit din test_init_env_auto.prg). Screenshot: COMUN\utile\Teste\editare_factura\watchdog_out\test_page3_articole_dialog1.png.

3. PROGRAM()/LINENO() din log (dupa Cancel pe dialog 0)

Din test_page3_articole_log.txt, PROGRAM()=VERIFICA_PAGECOUNT_FORM (procedura de test) pe liniile din jurul apelului loForm = Createobject([frm_modific2024], lnIdSet):

EROARE 1 [VERIFICA_PAGECOUNT_FORM:165] File 'crsjtvatemp.dbf' does not exist.
EROARE 1924 [VERIFICA_PAGECOUNT_FORM:166..173] LOFORM is not an object.   (cascada, zgomot)

4. Experimente de izolare (cerute de team-lead) - INFIRMA ipoteza initiala

Ipoteza initiala din aceasta sectiune ("SQLEXEC din update_jtva_coloane nu rezolva ?gnAn") era o deductie, nu o masuratoare - team-lead a cerut-o verificata direct, corect. Trei experimente ieftine, in ordine:

Experiment A - "chiar exista in acel moment?" TYPE('gnAn')/TRANSFORM(gnAn) puse imediat INAINTE de apelul update_jtva_coloane (linia 161 curenta, nu inainte de Createobject cum fusese verificat prima data):

EROARE 12 [VERIFICA_PAGECOUNT_FORM:161] Variable 'GNAN' is not found.
  • Eroare aparuta DOAR la primul apel al verifica_pagecount_form (cod=1140888), inainte sa se ajunga la update_jtva_coloane. Rezultat: gnAn e deja invizibila INAINTE ca update_jtva_coloane sa fie apelata - markerele ?gnAn/?gnLuna din updateserver.prg:597 nu pot fi (macar nu singure) cauza, contrazice ipoteza initiala.

Experiment B - "se reproduce izolat, fara nimic din S4?" Script nou, COMUN\utile\Teste\editare_factura\test_repro_izolat_gnan.prg: DOAR test_init_env_auto + update_jtva_coloane("", "crsJtvaTemp", 6), fara IncarcaCursoareModificareNota, fara frm_modific2024, fara nimic din PAGE3. Rezultat:

TYPE(gnAn)=N TRANSFORM(gnAn)=2026 TYPE(gnLuna)=N TRANSFORM(gnLuna)=8
dupa update_jtva_coloane: Used(crsJtvaTemp)=.T. Reccount=120
done

NU reproduce. Zero dialog, exit curat, cursorul se creeaza corect cu 120 randuri. update_jtva_coloane singura, chemata imediat dupa initul mediului, functioneaza perfect - nu e o capcana preexistenta a harness-ului si nici o vina proprie a functiei in izolare. Rerulat identic dupa scoaterea input-ului real din watchdog (vezi sectiunea 1) - acelasi rezultat, deci reconfirmat, nu era un artefact al mecanismului de dismiss.

Experiment C - oprit inainte de a-l rula. Premisa lui ("copiaza corpul lui update_jtva_coloane cu concatenare in loc de ?param, ca sa confirmi mecanismul") presupune ca vina e in legarea SQLEXEC a functiei - exact ce B tocmai a infirmat. Nu are sens sa continue in forma ceruta initial fara o noua directie.

5. Cauza CONFIRMATA prin bisectie (nu doar deductie)

Bisectie ceruta de team-lead: TYPE('gnAn')/TRANSFORM(gnAn) logat in doua straturi - (a) in programul principal, intre fiecare apel de nivel superior, si (b) ca prima linie in interiorul fiecarei proceduri (verifica_vanzare_nota, verifica_coliziune_cod, verifica_pagecount_form). Helper bisect_log_gnan (nou, la coada test_page3_articole.prg) - TYPE() e sigur necoditionat, TRANSFORM() doar daca TYPE()<>'U', ca sa nu produca o eroare noua care ar intrerupe bisectia.

Rezultat brut (test_page3_articole_log.txt):

[BISECT] main: dupa test_init_env_auto                          :: TYPE(gnAn)=N gnAn=2026
[BISECT] verifica_vanzare_nota ENTRY cod=1140888                :: TYPE(gnAn)=U gnAn=(U)
[BISECT] main: dupa verifica_vanzare_nota #1 (cod=1140888)      :: TYPE(gnAn)=N gnAn=2026
[BISECT] verifica_vanzare_nota ENTRY cod=1140885                :: TYPE(gnAn)=U gnAn=(U)
[BISECT] main: dupa verifica_vanzare_nota #2 (cod=1140885)      :: TYPE(gnAn)=N gnAn=2026
[BISECT] verifica_vanzare_nota ENTRY cod=1125486                :: TYPE(gnAn)=N gnAn=2026   <- diferit!
[BISECT] main: dupa verifica_vanzare_nota #3 (cod=1125486)      :: TYPE(gnAn)=N gnAn=2026
[BISECT] verifica_coliziune_cod ENTRY                            :: TYPE(gnAn)=N gnAn=2026
[BISECT] main: dupa verifica_coliziune_cod                       :: TYPE(gnAn)=N gnAn=2026
[BISECT] main: inainte de verifica_pagecount_form                :: TYPE(gnAn)=N gnAn=2026
[BISECT] verifica_pagecount_form ENTRY cod=1140888                :: TYPE(gnAn)=U gnAn=(U)
[BISECT] verifica_pagecount_form inainte de update_jtva_coloane   :: TYPE(gnAn)=U gnAn=(U)

Tiparul e limpede si consecvent: gnAn e INTOTDEAUNA valid (N, 2026) in scope-ul PRINCIPAL, la fiecare checkpoint, fara exceptie - deci NU e "eliberata" (nu e CLEAR ALL/CLEAR MEMORY/ RELEASE ALL EXTENDED pe undeva). E 'U' STRICT la intrarea in proceduri apelate cu DO ... WITH in care gnAn/gnLuna sunt trecute NEPARANTEZATE ca argumente, si redevine valid imediat ce procedura respectiva se termina si controlul revine in principal. Corelatia e exacta cu sintaxa apelului, nu cu ce face procedura pe dinauntru:

  • verifica_vanzare_nota apelul #1/#2 (gnAn -> U): call-site-urile trec gnAn, gnLuna DIRECT - test_page3_articole.prg:34 (DO verifica_vanzare_nota WITH 1140888, gnAn, gnLuna, ...) si test_page3_articole.prg:36 (... WITH 1140885, gnAn, gnLuna, ...).
  • verifica_vanzare_nota apelul #3 (gnAn ramane N): test_page3_articole.prg:38 trece 2008, 2 LITERAL, nu gnAn/gnLuna.
  • verifica_coliziune_cod (gnAn ramane N): test_page3_articole.prg:42 nu trece deloc gnAn/gnLuna (doar lcLog).
  • verifica_pagecount_form primul apel (gnAn -> U, exact scenariul blocat): test_page3_articole.prg:47 - DO verifica_pagecount_form WITH 1140888, gnAn, gnLuna, 'A (factura reala)', lcLog, 3, .T..

Mecanismul: DO <procedura> WITH <arg1>, <arg2>, ... (stilul vechi, folosit peste tot in acest script) trece variabilele de memorie BY REFERENCE implicit (SET UDFPARMS e REFERENCE in mod implicit VFP) - LPARAMETERS tnAn, tnLuna din procedura primitoare devin ALIAS-uri directe pe storage-ul lui gnAn/gnLuna, iar numele ORIGINAL devine inaccesibil (TYPE()='U') pe toata durata apelului, exact cat tine executia procedurii - confirmat empiric de simetria perfecta "intra U, revine N" la fiecare din cele 3 perechi de apeluri afectate.

Clasificare in termenii cerutii de team-lead: e "umbrire de scope" (categoria 2), NU "eliberare" (categoria 1) - dar mecanismul exact nu e o coliziune de nume PRIVATE/LOCAL in corpul procedurii (team-lead avea deja dreptate: verifica_pagecount_form nu are gnAn in LOCAL, si nu exista PRIVATE gnAn/LOCAL gnAn nicaieri in COMUN\programe) - umbrirea vine din SINTAXA APELULUI (DO...WITH fara paranteze in jurul lui gnAn/gnLuna), nu din declaratiile procedurii apelate.

Statement-ul vinovat exact, cu fisier:linie: COMUN\utile\Teste\editare_factura\test_page3_articole.prg:47.

IMPLICATIE IMPORTANTA: acesta e un tipar din SCRIPTUL DE TEST, nu din fluxul real al aplicatiei - do_editare_factura (codul de productie) nu trece prin verifica_pagecount_form (helper propriu testului). Team-lead a confirmat verdictul: e strict un defect de harness - updateserver.prg, omodificari.* si codul S4 sunt toate nevinovate.

Remediu APLICAT de team-lead (3 linii, sub pragul lui de editare directa): argumentele gnAn/gnLuna sunt acum parantezate - (gnAn), (gnLuna) - la liniile 37, 39 si 50 din test_page3_articole.prg (parantezele forteaza trecere PRIN VALOARE in loc de PRIN REFERINTA), cu un comentariu explicativ deasupra primei aparitii. Nerulat inca de mine (interzis explicit - suita o ruleaza team-lead-ul dupa eliberarea .fxp-ului).

Remediul din rundele anterioare ale acestui raport (concatenare in loc de ?gnAn/?gnLuna in updateserver.prg:597) ramane infirmat - nu era cauza. updateserver.prg nu a fost si nu e atins.

6. Fisiere atinse

  • Nou: COMUN\utile\Teste\watchdog_vfp.ps1, COMUN\utile\Teste\watchdog_selftest.prg, COMUN\utile\Teste\editare_factura\test_repro_izolat_gnan.prg (experimentul B, izolat).
  • Modificat: COMUN\utile\Teste\editare_factura\test_page3_articole.prg
    • linia 176 (update_jtva_coloane(..., 6), ramane - fix necesar pt. indexul id_jtva, independent de defectul de mai jos);
    • PROCEDURE bisect_log_gnan (noua, la coada fisierului) + apeluri DO bisect_log_gnan WITH ... inserate in principal (intre apelurile de nivel superior) si ca prima linie in fiecare procedura
      • ramase in cod, sunt dovada bisectiei;
    • liniile 37, 39, 50 - remediul APLICAT de team-lead: gnAn/gnLuna parantezate ((gnAn), (gnLuna)), forteaza trecere prin valoare in loc de prin referinta in DO...WITH.
  • Neatins: COMUN\clase\omodificari.vc2/.vcx/.vct, COMUN\programe\updateserver.prg (ambele infirmate ca posibila cauza, vezi sectiunea 5).
  • Artefacte de rulare (watchdog_out\*.png/.log, *_log.txt) raman pe disc ca dovada; se pot sterge cu curatenie.ps1 la finalul lucrarii.

7. Stare la data acestui raport

Cauza confirmata prin bisectie (sectiunea 5) si remediul APLICAT de team-lead (paranteze la liniile 37/39/50). Nerulat inca de nimeni dupa aplicarea remediului - team-lead ruleaza suita separat, dupa eliberarea .fxp-ului (nu s-a mai relansat testul in aceasta sesiune, per interdictia primita). Ramane deschis, pentru cine continua:

  1. Confirma cu o rulare ca remediul chiar elimina dialogul si verifica_pagecount_form trece PASS pe PageCount=3/lAreArticoleVanzari=.T. pentru cod=1140888.
  2. Optional: verifica daca fluxul REAL de productie (do_editare_factura in ofacturare_comun.vc2) are undeva acelasi tipar DO...WITH <variabila PUBLIC> NEPARANTEZAT inainte de un ?param in SQLEXEC - team-lead a verdictuit "defect de harness", dar asta ramane neverificat exhaustiv pe codul de productie.
  3. Watchdog-ul (watchdog_vfp.ps1) ramane instrumentul de verificat orice ipoteza noua fara sa se agate procesul si FARA input real (regula obligatorie, sectiunea 1) - reutilizabil pentru orice alt blocaj similar in suita.