fivetech commited on
Commit
0dfbe21
·
verified ·
1 Parent(s): 72098dc

Upload test_battery.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. test_battery.py +516 -0
test_battery.py ADDED
@@ -0,0 +1,516 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ Harbour Test Battery - Generates code, compiles with harbour, evaluates with qwen3.6:35b
4
+ """
5
+
6
+ import json
7
+ import time
8
+ import subprocess
9
+ import requests
10
+ import tempfile
11
+ import os
12
+ from pathlib import Path
13
+ from datetime import datetime
14
+
15
+ OLLAMA_URL = "http://localhost:11434/api/generate"
16
+ MODEL = "qwen3.6:35b"
17
+ HARBOUR = "/home/fivetech/harbour/bin/linux/gcc/harbour"
18
+ WORK_DIR = Path("/home/fivetech/finetune/test_output")
19
+ WORK_DIR.mkdir(exist_ok=True)
20
+
21
+ def query_ollama(prompt, system="", timeout=300):
22
+ payload = {
23
+ "model": MODEL,
24
+ "prompt": prompt,
25
+ "stream": False,
26
+ "options": {"temperature": 0.2, "num_predict": 3000, "top_p": 0.9}
27
+ }
28
+ if system:
29
+ payload["system"] = system
30
+ try:
31
+ start = time.time()
32
+ r = requests.post(OLLAMA_URL, json=payload, timeout=timeout)
33
+ elapsed = time.time() - start
34
+ data = r.json()
35
+ return {
36
+ "response": data.get("response", ""),
37
+ "eval_count": data.get("eval_count", 0),
38
+ "duration": elapsed,
39
+ "tps": data.get("eval_count", 0) / max(data.get("eval_duration", 1) / 1e9, 0.001),
40
+ "error": None
41
+ }
42
+ except Exception as e:
43
+ return {"response": "", "error": str(e), "eval_count": 0, "duration": 0, "tps": 0}
44
+
45
+ def compile_harbour(code):
46
+ """Compile code with harbour, return (success, error_msg, obj_exists)"""
47
+ prg_file = WORK_DIR / "test.prg"
48
+ prg_file.write_text(code)
49
+
50
+ try:
51
+ result = subprocess.run(
52
+ [HARBOUR, str(prg_file), "-n", "-w"],
53
+ capture_output=True, text=True, timeout=30
54
+ )
55
+ obj_file = WORK_DIR / "test.obj"
56
+ success = result.returncode == 0
57
+ obj_exists = obj_file.exists()
58
+ error = result.stderr.strip() if result.stderr else ""
59
+ if not success and not error:
60
+ error = result.stdout.strip()
61
+ return success, error, obj_exists
62
+ except subprocess.TimeoutExpired:
63
+ return False, "Compilation timeout", False
64
+ except Exception as e:
65
+ return False, str(e), False
66
+
67
+ def clean_code(response):
68
+ """Extract code from model response, remove markdown."""
69
+ lines = response.split('\n')
70
+ in_code = False
71
+ code_lines = []
72
+ skip_explanation = True
73
+
74
+ for line in lines:
75
+ stripped = line.strip()
76
+
77
+ # Skip markdown
78
+ if stripped.startswith('```'):
79
+ in_code = not in_code
80
+ continue
81
+
82
+ if in_code:
83
+ code_lines.append(line)
84
+ skip_explanation = False
85
+ elif skip_explanation:
86
+ # Detect start of code
87
+ upper = stripped.upper()
88
+ if any(upper.startswith(kw) for kw in [
89
+ 'FUNCTION', 'PROCEDURE', 'LOCAL', 'STATIC', 'PUBLIC',
90
+ 'PRIVATE', 'MEMVAR', '#DEFINE', '#INCLUDE', 'CLASS',
91
+ 'METHOD', 'RETURN', 'SET', 'REQUEST'
92
+ ]):
93
+ in_code = True
94
+ code_lines.append(line)
95
+ skip_explanation = False
96
+
97
+ if not code_lines:
98
+ # Fallback: take everything
99
+ code_lines = response.split('\n')
100
+
101
+ return '\n'.join(code_lines).strip()
102
+
103
+ # ============================================================
104
+ # TEST DEFINITIONS - Based on dataset patterns
105
+ # ============================================================
106
+
107
+ TESTS = [
108
+ # ---- BASIC SYNTAX ----
109
+ {
110
+ "id": "SYNTAX_01", "category": "Basic Syntax", "name": "Variable types and declarations",
111
+ "prompt": "Write a Harbour program that declares LOCAL variables of each type (numeric, character, logical, date, nil), prints them with ValType(), and uses proper Hungarian notation.",
112
+ "expected_keywords": ["LOCAL", "ValType", "FUNCTION"],
113
+ "min_lines": 8,
114
+ },
115
+ {
116
+ "id": "SYNTAX_02", "category": "Basic Syntax", "name": "Preprocessor defines",
117
+ "prompt": "Write Harbour preprocessor definitions for application constants: app name, version, max records, date format. Use #define and show conditional compilation with #ifdef.",
118
+ "expected_keywords": ["#DEFINE", "#IFDEF", "#ENDIF"],
119
+ "min_lines": 6,
120
+ },
121
+ {
122
+ "id": "SYNTAX_03", "category": "Basic Syntax", "name": "String operations",
123
+ "prompt": "Write a Harbour function that takes a full name string and returns initials. Use AllTrim, Upper, Left, At, SubStr, and Space functions.",
124
+ "expected_keywords": ["FUNCTION", "AllTrim", "Upper", "Left", "At", "SubStr"],
125
+ "min_lines": 6,
126
+ },
127
+ {
128
+ "id": "SYNTAX_04", "category": "Basic Syntax", "name": "Date functions",
129
+ "prompt": "Write a Harbour function that calculates the number of business days between two dates, excluding weekends. Use Date(), DOW(), and date arithmetic.",
130
+ "expected_keywords": ["FUNCTION", "Date", "DOW"],
131
+ "min_lines": 8,
132
+ },
133
+ {
134
+ "id": "SYNTAX_05", "category": "Basic Syntax", "name": "Type conversion",
135
+ "prompt": "Write Harbour code that converts between all types: Str, Val, CTOD, DTOC, ASC, Chr, Transform. Show edge cases.",
136
+ "expected_keywords": ["Str", "Val", "CTOD", "DTOC"],
137
+ "min_lines": 8,
138
+ },
139
+
140
+ # ---- CONTROL FLOW ----
141
+ {
142
+ "id": "CTRL_01", "category": "Control Flow", "name": "IF/ELSEIF/ENDIF",
143
+ "prompt": "Write a Harbour function that classifies employee salary into tax brackets using IF/ELSEIF/ELSE/ENDIF. Include 5 brackets and error handling.",
144
+ "expected_keywords": ["FUNCTION", "IF", "ELSEIF", "ELSE", "ENDIF"],
145
+ "min_lines": 10,
146
+ },
147
+ {
148
+ "id": "CTRL_02", "category": "Control Flow", "name": "DO CASE",
149
+ "prompt": "Write a Harbour function using DO CASE to convert month number (1-12) to season name. Handle invalid input with OTHERWISE.",
150
+ "expected_keywords": ["DO CASE", "CASE", "OTHERWISE", "ENDCASE"],
151
+ "min_lines": 8,
152
+ },
153
+ {
154
+ "id": "CTRL_03", "category": "Control Flow", "name": "FOR/NEXT loop",
155
+ "prompt": "Write a Harbour function using FOR/NEXT to calculate the sum of all prime numbers below 100. Include STEP and EXIT.",
156
+ "expected_keywords": ["FOR", "TO", "NEXT", "IF", "EXIT"],
157
+ "min_lines": 10,
158
+ },
159
+ {
160
+ "id": "CTRL_04", "category": "Control Flow", "name": "DO WHILE",
161
+ "prompt": "Write a Harbour function using DO WHILE to implement the Euclidean algorithm for GCD. Include LOOP and EXIT.",
162
+ "expected_keywords": ["DO WHILE", "ENDDO", "IF", "LOOP", "EXIT"],
163
+ "min_lines": 6,
164
+ },
165
+ {
166
+ "id": "CTRL_05", "category": "Control Flow", "name": "SCAN/ENDSCAN",
167
+ "prompt": "Write Harbour code using SCAN/ENDSCAN to find the longest string in an array. Include NEXT clause.",
168
+ "expected_keywords": ["SCAN", "ENDSCAN"],
169
+ "min_lines": 6,
170
+ },
171
+ {
172
+ "id": "CTRL_06", "category": "Control Flow", "name": "FOR EACH",
173
+ "prompt": "Write Harbour code using FOR EACH to count word frequencies in a string. Use a hash for storage.",
174
+ "expected_keywords": ["FOR EACH", "NEXT", ":="],
175
+ "min_lines": 8,
176
+ },
177
+
178
+ # ---- FUNCTIONS ----
179
+ {
180
+ "id": "FUNC_01", "category": "Functions", "name": "Parameters and return",
181
+ "prompt": "Write a Harbour function with default parameters, pass-by-reference using @, and return an array. Include proper Hungarian notation.",
182
+ "expected_keywords": ["FUNCTION", "LOCAL", "RETURN"],
183
+ "min_lines": 6,
184
+ },
185
+ {
186
+ "id": "FUNC_02", "category": "Functions", "name": "Recursion",
187
+ "prompt": "Write a recursive Harbour function for Fibonacci numbers with memoization using a hash. Include base case and error handling.",
188
+ "expected_keywords": ["FUNCTION", "IF", "RETURN"],
189
+ "min_lines": 8,
190
+ },
191
+ {
192
+ "id": "FUNC_03", "category": "Functions", "name": "Variable scope",
193
+ "prompt": "Write Harbour code demonstrating LOCAL, STATIC, PRIVATE, PUBLIC variables. Show scope differences with nested function calls.",
194
+ "expected_keywords": ["LOCAL", "STATIC", "PRIVATE", "PUBLIC"],
195
+ "min_lines": 8,
196
+ },
197
+ {
198
+ "id": "FUNC_04", "category": "Functions", "name": "Code blocks",
199
+ "prompt": "Write Harbour code using code blocks: AEval with {|x| x*2}, AScan, ASort with custom sort. Show evaluation with Eval().",
200
+ "expected_keywords": ["AEval", "AScan", "ASort", "Eval"],
201
+ "min_lines": 6,
202
+ },
203
+ {
204
+ "id": "FUNC_05", "category": "Functions", "name": "Error handling",
205
+ "prompt": "Write a Harbour function with BEGIN SEQUENCE/RECOVER/END SEQUENCE for file reading. Include DEFAULT and BREAK.",
206
+ "expected_keywords": ["BEGIN SEQUENCE", "RECOVER", "END SEQUENCE"],
207
+ "min_lines": 8,
208
+ },
209
+
210
+ # ---- ARRAYS ----
211
+ {
212
+ "id": "ARRAY_01", "category": "Arrays", "name": "Array operations",
213
+ "prompt": "Write Harbour functions for: create 2D array, AAdd elements, ASort with custom order, AScan by value, ASize to resize. Include error handling.",
214
+ "expected_keywords": ["ARRAY", "AAdd", "ASort", "AScan", "ASize"],
215
+ "min_lines": 8,
216
+ },
217
+ {
218
+ "id": "ARRAY_02", "category": "Arrays", "name": "Hash operations",
219
+ "prompt": "Write Harbour code using hashes: create, add keys, iterate with FOR EACH, merge two hashes, check key existence with HB_HHasKey, convert to array.",
220
+ "expected_keywords": [":=", "FOR EACH", "HB_HHasKey"],
221
+ "min_lines": 8,
222
+ },
223
+ {
224
+ "id": "ARRAY_03", "category": "Arrays", "name": "Sorting algorithm",
225
+ "prompt": "Implement QuickSort in Harbour for an array of numbers. Include partition logic and proper recursion.",
226
+ "expected_keywords": ["FUNCTION", "LOCAL", "IF", "RETURN"],
227
+ "min_lines": 12,
228
+ },
229
+
230
+ # ---- OOP ----
231
+ {
232
+ "id": "OOP_01", "category": "OOP", "name": "Class definition",
233
+ "prompt": "Write a Harbour class Person with DATA (name, age), METHOD (New constructor, GetName, SetAge), and CLASSDATA. Include validation in SetAge.",
234
+ "expected_keywords": ["CLASS", "DATA", "METHOD", "RETURN"],
235
+ "min_lines": 10,
236
+ },
237
+ {
238
+ "id": "OOP_02", "category": "OOP", "name": "Inheritance",
239
+ "prompt": "Write Harbour classes: Shape (base), Circle (derived) with area() method. Show inheritance syntax and method override.",
240
+ "expected_keywords": ["CLASS", "METHOD", "INHERIT"],
241
+ "min_lines": 10,
242
+ },
243
+ {
244
+ "id": "OOP_03", "category": "OOP", "name": "Operator overloading",
245
+ "prompt": "Write a Harbour class Vec2 for 2D vectors. Overload + and - operators. Include magnitude and normalize methods.",
246
+ "expected_keywords": ["CLASS", "METHOD", "OPERATOR"],
247
+ "min_lines": 12,
248
+ },
249
+ {
250
+ "id": "OOP_04", "category": "OOP", "name": "Singleton pattern",
251
+ "prompt": "Implement Singleton pattern in Harbour for a config manager. Ensure only one instance exists.",
252
+ "expected_keywords": ["CLASS", "CLASSDATA", "METHOD"],
253
+ "min_lines": 10,
254
+ },
255
+
256
+ # ---- DATABASE ----
257
+ {
258
+ "id": "DB_01", "category": "Database", "name": "Basic RDD",
259
+ "prompt": "Write Harbour code that creates a DBF file, opens it, appends records, and closes properly. Use DBCreate and DBUseArea.",
260
+ "expected_keywords": ["DBCreate", "DBUseArea", "DBAppend", "DBCLOSEALL"],
261
+ "min_lines": 10,
262
+ },
263
+ {
264
+ "id": "DB_02", "category": "Database", "name": "Indexing",
265
+ "prompt": "Write Harbour code creating an index on a DBF field using RDD. Include ORDSCOPE for range queries.",
266
+ "expected_keywords": ["ORDCREATE", "ORDSCOPE"],
267
+ "min_lines": 8,
268
+ },
269
+ {
270
+ "id": "DB_03", "category": "Database", "name": "DBEval",
271
+ "prompt": "Write Harbour code using DBEval to process all records: count, sum field values, and mark records meeting a condition.",
272
+ "expected_keywords": ["DBEval", "FOR", "WHILE"],
273
+ "min_lines": 8,
274
+ },
275
+
276
+ # ---- FILE I/O ----
277
+ {
278
+ "id": "FILE_01", "category": "File I/O", "name": "Text file read/write",
279
+ "prompt": "Write Harbour functions to read a text file line by line and write processed output. Use FCreate, FOpen, FRead, FWrite, FClose, FEof.",
280
+ "expected_keywords": ["FCreate", "FOpen", "FRead", "FWrite", "FClose", "FEof"],
281
+ "min_lines": 10,
282
+ },
283
+ {
284
+ "id": "FILE_02", "category": "File I/O", "name": "Directory listing",
285
+ "prompt": "Write Harbour code using Directory() to list files with a pattern, get file size and date, and process each file.",
286
+ "expected_keywords": ["Directory", "LEN", "FOR"],
287
+ "min_lines": 6,
288
+ },
289
+
290
+ # ---- COMPLEX ----
291
+ {
292
+ "id": "CMPX_01", "category": "Complex", "name": "CSV parser",
293
+ "prompt": "Write a Harbour CSV parser that reads a CSV file, handles quoted fields, and returns an array of arrays. Include error handling.",
294
+ "expected_keywords": ["FUNCTION", "LOCAL", "FClose", "FEof"],
295
+ "min_lines": 15,
296
+ },
297
+ {
298
+ "id": "CMPX_02", "category": "Complex", "name": "INI file reader",
299
+ "prompt": "Write a Harbour INI file parser. Read sections, keys, and values into a hash. Handle comments and empty lines.",
300
+ "expected_keywords": ["FUNCTION", "LOCAL", "HASH"],
301
+ "min_lines": 12,
302
+ },
303
+ {
304
+ "id": "CMPX_03", "category": "Complex", "name": "String template engine",
305
+ "prompt": "Write a Harbour template engine replacing {{variable}} placeholders with hash values. Include error handling for missing keys.",
306
+ "expected_keywords": ["FUNCTION", "LOCAL", "STRTRAN"],
307
+ "min_lines": 8,
308
+ },
309
+ {
310
+ "id": "CMPX_04", "category": "Complex", "name": "Logger",
311
+ "prompt": "Write a Harbour logging system with DEBUG/INFO/WARN/ERROR levels, timestamp, file output, and configurable level filtering.",
312
+ "expected_keywords": ["FUNCTION", "LOCAL", "FClose"],
313
+ "min_lines": 12,
314
+ },
315
+ {
316
+ "id": "CMPX_05", "category": "Complex", "name": "Base64 encoder",
317
+ "prompt": "Write a Harbour Base64 encoder/decode function. Use Asc(), Chr(), and bit operations.",
318
+ "expected_keywords": ["FUNCTION", "LOCAL", "Asc", "Chr"],
319
+ "min_lines": 10,
320
+ },
321
+ {
322
+ "id": "CMPX_06", "category": "Complex", "name": "JSON serializer",
323
+ "prompt": "Write a Harbour function that serializes a hash to JSON string. Handle strings, numbers, booleans, arrays, and nested objects.",
324
+ "expected_keywords": ["FUNCTION", "LOCAL", "HB_IsHash"],
325
+ "min_lines": 15,
326
+ },
327
+ {
328
+ "id": "CMPX_07", "category": "Complex", "name": "LRU Cache",
329
+ "prompt": "Write a Harbour LRU cache class with get/set/delete, TTL expiration, and max size. Use a hash and an array for ordering.",
330
+ "expected_keywords": ["CLASS", "DATA", "METHOD"],
331
+ "min_lines": 15,
332
+ },
333
+ {
334
+ "id": "CMPX_08", "category": "Complex", "name": "SQL-like query on arrays",
335
+ "prompt": "Write a Harbour function that filters an array of hashes like SQL WHERE clause. Support =, <>, >, <, LIKE operators.",
336
+ "expected_keywords": ["FUNCTION", "LOCAL", "FOR"],
337
+ "min_lines": 12,
338
+ },
339
+ {
340
+ "id": "CMPX_09", "category": "Complex", "name": "Rate limiter",
341
+ "prompt": "Write a Harbour rate limiter class: max N requests per M seconds. Use timestamps and a queue.",
342
+ "expected_keywords": ["CLASS", "METHOD", "LOCAL"],
343
+ "min_lines": 12,
344
+ },
345
+ {
346
+ "id": "CMPX_10", "category": "Complex", "name": "Config file writer",
347
+ "prompt": "Write a Harbour config manager that saves/loads settings to JSON file. Include defaults, validation, and typed getters.",
348
+ "expected_keywords": ["FUNCTION", "LOCAL", "FClose"],
349
+ "min_lines": 12,
350
+ },
351
+
352
+ # ---- BUGGY CODE TO FIX ----
353
+ {
354
+ "id": "FIX_01", "category": "Bug Fix", "name": "Null pointer",
355
+ "prompt": "Fix this Harbour code that crashes when array is empty:\nLOCAL a := {}\n? a[1]",
356
+ "expected_keywords": ["IF", "LEN", "RETURN"],
357
+ "min_lines": 3,
358
+ },
359
+ {
360
+ "id": "FIX_02", "category": "Bug Fix", "name": "Wrong loop bounds",
361
+ "prompt": "Fix this code that skips last element:\nLOCAL a := {10,20,30}\nFOR i := 1 TO LEN(a)-1\n ? a[i]\nNEXT",
362
+ "expected_keywords": ["FOR", "TO", "LEN"],
363
+ "min_lines": 3,
364
+ },
365
+ {
366
+ "id": "FIX_03", "category": "Bug Fix", "name": "String concat error",
367
+ "prompt": "Fix this code that fails on nil values:\nLOCAL cName := NIL\n? 'Hello ' + cName",
368
+ "expected_keywords": ["IF", "LOCAL", "RETURN"],
369
+ "min_lines": 3,
370
+ },
371
+
372
+ # ---- HARBOUR-SPECIFIC ----
373
+ {
374
+ "id": "HARB_01", "category": "Harbour-Specific", "name": "HB_* functions",
375
+ "prompt": "Write Harbour code using HB_IsString, HB_IsNumeric, HB_IsArray, HB_IsHash, HB_IsNil to validate function arguments. Include proper error messages.",
376
+ "expected_keywords": ["HB_IsString", "HB_IsNumeric", "IF"],
377
+ "min_lines": 6,
378
+ },
379
+ {
380
+ "id": "HARB_02", "category": "Harbour-Specific", "name": "Regex",
381
+ "prompt": "Write a Harbour function using HB_RegEx to validate email addresses. Use HB_RegExCompile and HB_RegExMatch.",
382
+ "expected_keywords": ["HB_RegEx", "FUNCTION"],
383
+ "min_lines": 6,
384
+ },
385
+ {
386
+ "id": "HARB_03", "category": "Harbour-Specific", "name": "Serialization",
387
+ "prompt": "Write Harbour code that serializes a hash to binary with HB_Serialize and deserializes with HB_Deserialize.",
388
+ "expected_keywords": ["HB_Serialize", "HB_Deserialize"],
389
+ "min_lines": 6,
390
+ },
391
+ {
392
+ "id": "HARB_04", "category": "Harbour-Specific", "name": "File path operations",
393
+ "prompt": "Write Harbour code using hb_DirBuild, hb_DirNameGet, hb_FileNameGet, hb_PathNormalize for cross-platform file handling.",
394
+ "expected_keywords": ["hb_Dir", "hb_File", "hb_Path"],
395
+ "min_lines": 6,
396
+ },
397
+ ]
398
+
399
+ # ============================================================
400
+ # MAIN
401
+ # ============================================================
402
+
403
+ def main():
404
+ print("=" * 70)
405
+ print("HARBOUR CODE GENERATION TEST BATTERY")
406
+ print(f"Model: {MODEL}")
407
+ print(f"Tests: {len(TESTS)}")
408
+ print(f"Harbour: {HARBOUR}")
409
+ print(f"Started: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
410
+ print("=" * 70)
411
+
412
+ SYSTEM = """You are an expert Harbour programmer. Write clean, correct, COMPILABLE Harbour code.
413
+ Use Hungarian notation: n=numeric, c=character, l=logical, a=array, o=object, d=date.
414
+ Use 3-space indentation.
415
+ Do NOT include explanations or markdown. Only raw Harbour code.
416
+ End functions with RETURN and END FUNCTION."""
417
+
418
+ results = []
419
+ compile_pass = 0
420
+ compile_fail = 0
421
+
422
+ for i, test in enumerate(TESTS, 1):
423
+ print(f"\n[{i:2d}/{len(TESTS)}] {test['id']}: {test['name']}")
424
+
425
+ # Query model
426
+ result = query_ollama(test["prompt"], SYSTEM)
427
+
428
+ if result["error"]:
429
+ print(f" MODEL ERROR: {result['error']}")
430
+ results.append({"test": test, "model_error": result["error"], "compile": False, "compile_error": ""})
431
+ continue
432
+
433
+ # Clean response
434
+ code = clean_code(result["response"])
435
+
436
+ # Check for expected keywords
437
+ keywords_found = [kw for kw in test["expected_keywords"] if kw.upper() in code.upper()]
438
+ keywords_missing = [kw for kw in test["expected_keywords"] if kw.upper() not in code.upper()]
439
+
440
+ # Compile
441
+ success, error, obj = compile_harbour(code)
442
+
443
+ status = "PASS" if success else "FAIL"
444
+ if success:
445
+ compile_pass += 1
446
+ else:
447
+ compile_fail += 1
448
+
449
+ print(f" Compile: {status} | Keywords: {len(keywords_found)}/{len(test['expected_keywords'])} | TPS: {result['tps']:.0f}")
450
+ if keywords_missing:
451
+ print(f" Missing keywords: {', '.join(keywords_missing)}")
452
+ if not success and error:
453
+ # Show first error only
454
+ first_error = error.split('\n')[0][:120]
455
+ print(f" Error: {first_error}")
456
+
457
+ results.append({
458
+ "test": test,
459
+ "code": code[:3000],
460
+ "compile_success": success,
461
+ "compile_error": error[:500] if error else "",
462
+ "keywords_found": keywords_found,
463
+ "keywords_missing": keywords_missing,
464
+ "tokens": result["eval_count"],
465
+ "tps": result["tps"],
466
+ "duration": result["duration"],
467
+ "lines": code.count('\n') + 1,
468
+ })
469
+
470
+ # Summary by category
471
+ print("\n" + "=" * 70)
472
+ print("RESULTS SUMMARY")
473
+ print("=" * 70)
474
+
475
+ categories = {}
476
+ for r in results:
477
+ cat = r["test"]["category"]
478
+ if cat not in categories:
479
+ categories[cat] = {"pass": 0, "fail": 0, "total": 0}
480
+ categories[cat]["total"] += 1
481
+ if r.get("compile_success"):
482
+ categories[cat]["pass"] += 1
483
+ else:
484
+ categories[cat]["fail"] += 1
485
+
486
+ print(f"\n{'Category':<20} {'Pass':<6} {'Fail':<6} {'Rate':<8}")
487
+ print("-" * 45)
488
+ for cat, data in sorted(categories.items()):
489
+ rate = data["pass"] / data["total"] * 100 if data["total"] > 0 else 0
490
+ print(f"{cat:<20} {data['pass']:<6} {data['fail']:<6} {rate:.0f}%")
491
+
492
+ print(f"\n{'TOTAL':<20} {compile_pass:<6} {compile_fail:<6} {compile_pass/len(results)*100:.0f}%")
493
+ print(f"Total tests: {len(results)}")
494
+
495
+ total_tokens = sum(r.get("tokens", 0) for r in results)
496
+ total_time = sum(r.get("duration", 0) for r in results)
497
+ print(f"Total tokens: {total_tokens:,}")
498
+ print(f"Total time: {total_time:.1f}s")
499
+
500
+ # Save
501
+ output = Path("/home/fivetech/finetune/test_baseline_qwen36.json")
502
+ with open(output, "w") as f:
503
+ json.dump({
504
+ "model": MODEL,
505
+ "timestamp": datetime.now().isoformat(),
506
+ "compile_pass": compile_pass,
507
+ "compile_fail": compile_fail,
508
+ "compile_rate": compile_pass / len(results) * 100,
509
+ "categories": categories,
510
+ "results": results,
511
+ }, f, indent=2, ensure_ascii=False)
512
+
513
+ print(f"\nResults saved to: {output}")
514
+
515
+ if __name__ == "__main__":
516
+ main()