bayan-api / tests /test_concurrency.py
youssefreda9's picture
feat: P1-P3 all 8 items - ta marbuta fix, UI feedback, caching, rate limiting, unit tests
918deb4
Raw
History Blame Contribute Delete
7.1 kB
"""
Phase 9 — Concurrency Re-verification
Send 5 genuinely different inputs simultaneously.
Verify each response correctly corresponds to its own input.
No mixed, swapped, or cross-contaminated suggestions.
If cross-contamination is found, treat as P0 bug.
"""
import sys, os, json, time, requests
from concurrent.futures import ThreadPoolExecutor, as_completed
API_BASE = "https://bayan10-bayan-api.hf.space"
TIMEOUT = 60
def api_call(endpoint, text):
url = f"{API_BASE}{endpoint}"
try:
t0 = time.time()
resp = requests.post(url, json={"text": text}, timeout=TIMEOUT)
elapsed = int((time.time() - t0) * 1000)
if resp.status_code == 200:
data = resp.json()
data['_elapsed_ms'] = elapsed
return data
return {"error": f"HTTP {resp.status_code}", "_elapsed_ms": elapsed}
except Exception as e:
return {"error": f"{type(e).__name__}: {e}"}
# 5 genuinely different inputs — different lengths, different error types
CONCURRENT_INPUTS = [
{
"id": "CONC-1",
"text": "الحديقه جميله",
"description": "Short text with spelling error (ه→ة)",
"expected_contains": "الحديق", # at least part of the input
"must_not_contain_from_others": ["المدرسة", "القاهرة", "مصر"],
},
{
"id": "CONC-2",
"text": "الطلاب ذهبو الى المدرسة",
"description": "Medium text with grammar error (ذهبو→ذهبوا)",
"expected_contains": "المدرسة",
"must_not_contain_from_others": ["الحديق", "القاهرة عاصمة"],
},
{
"id": "CONC-3",
"text": "التزم الرياضي بتناول وجباته الصحية وحساب سعراته بدقة رغبة في بناء كتلة عضلية قوية ويا له من التزام حديدي يثير الإعجاب ولقد كان أداؤه في المسابقات الأخيرة مبهرا للغاية",
"description": "Long text with punctuation needed (50+ words)",
"expected_contains": "الرياضي",
"must_not_contain_from_others": ["الحديق", "المدرسة"],
},
{
"id": "CONC-4",
"text": "القاهرة عاصمة مصر",
"description": "Correct text (should return ~0 suggestions)",
"expected_contains": "القاهرة",
"must_not_contain_from_others": ["الحديق", "المدرسة", "الرياضي"],
},
{
"id": "CONC-5",
"text": "هذة المدينه جميله جدا ومناخها معتدل",
"description": "Text with mixed errors (هذة→هذه, ه→ة)",
"expected_contains": "المدين",
"must_not_contain_from_others": ["المدرسة", "القاهرة", "الرياضي"],
},
]
def test_concurrent():
print("=" * 70)
print("PHASE 9 — Concurrency Re-verification")
print("=" * 70)
# Fire all 5 requests simultaneously
results = {}
print(f"\nSending {len(CONCURRENT_INPUTS)} requests simultaneously...")
with ThreadPoolExecutor(max_workers=5) as executor:
futures = {}
for inp in CONCURRENT_INPUTS:
future = executor.submit(api_call, "/api/analyze", inp["text"])
futures[future] = inp
for future in as_completed(futures):
inp = futures[future]
result = future.result()
results[inp["id"]] = {
"input": inp,
"response": result,
}
print(f" {inp['id']} completed ({result.get('_elapsed_ms', '?')}ms)")
# Verify each response corresponds to its own input
print("\n--- Verification ---")
all_pass = True
contamination_found = False
for test_id, data in sorted(results.items()):
inp = data["input"]
resp = data["response"]
if "error" in resp and "status" not in resp:
print(f"\n ⚠ {test_id}: ERROR — {resp['error']}")
continue
corrected = resp.get("corrected", "")
original = resp.get("original", "")
suggestions = resp.get("suggestions", [])
print(f"\n {test_id}: {inp['description']}")
print(f" Input: '{inp['text'][:60]}...'")
print(f" Original: '{original[:60]}...'")
print(f" Corrected: '{corrected[:60]}...'")
print(f" Suggestions: {len(suggestions)}")
# Check 1: original field should match our input
if original != inp["text"]:
print(f" ❌ FAIL: original != input! (cross-contamination?)")
contamination_found = True
all_pass = False
else:
print(f" ✓ original matches input")
# Check 2: corrected should contain expected content
if inp["expected_contains"] in corrected:
print(f" ✓ corrected contains '{inp['expected_contains']}'")
else:
print(f" ⚠ corrected missing '{inp['expected_contains']}'")
# Check 3: corrected must NOT contain content from other inputs
for foreign in inp["must_not_contain_from_others"]:
if foreign in corrected:
print(f" ❌ CONTAMINATION: corrected contains '{foreign}' from another input!")
contamination_found = True
all_pass = False
# Check 4: suggestions should reference text in our input
for s in suggestions:
s_orig = s.get("original", "")
s_start = s.get("start", 0)
s_end = s.get("end", 0)
# The suggestion's original text should be a substring of our input
if s_orig and s_orig not in inp["text"]:
# Check if it's a substring match (punc may include partial words)
input_slice = inp["text"][s_start:s_end]
if s_orig != input_slice:
print(f" ⚠ Suggestion '{s_orig}' [{s_start}:{s_end}] not in input")
print("\n" + "=" * 50)
if contamination_found:
print("🚨 P0: CROSS-CONTAMINATION DETECTED!")
print(" PipelineContext state is leaking between requests.")
print(" STOP ALL OTHER WORK AND FIX THIS FIRST.")
elif all_pass:
print("✅ ALL PASSED — No cross-contamination detected.")
else:
print("⚠ Some checks failed but no cross-contamination.")
return {
"test_count": len(CONCURRENT_INPUTS),
"all_pass": all_pass,
"contamination_found": contamination_found,
"results": {k: {"corrected": v["response"].get("corrected", ""),
"suggestions_count": len(v["response"].get("suggestions", []))}
for k, v in results.items()},
}
if __name__ == "__main__":
result = test_concurrent()
output_path = os.path.join(os.path.dirname(__file__), 'phase9_results.json')
with open(output_path, 'w', encoding='utf-8') as f:
json.dump(result, f, ensure_ascii=False, indent=2)
print(f"\nResults saved to {output_path}")