#!/usr/bin/env python3
"""
Comprehensive search test script for Accordance Port — 20 real-world test cases.

Exercises the underlying Python APIs directly (not the MCP server).
Run from project root:
    cd "/Users/jacobgruber/Projects/Accordance Port" && .venv/bin/python3 test_search_cases.py
"""

import sys
import traceback

# ── Imports (once at top) ──────────────────────────────────────────────────
from accordance_parser.bible_api import get_verse
from accordance_parser.discovery import discover_modules
from accordance_parser.lexicon_module import LexiconModule
from accordance_parser.commentary_module import CommentaryModule
from accordance_parser.search_index import SearchIndex
from accordance_parser.transliteration import transliterate_hebrew

# ── Global state (initialized once, reused across tests) ───────────────────
mods = None          # All discovered modules dict
lex = None           # Lexicon/dictionary modules (type 3, 4)
com_mods = None      # Commentary modules (type 5)
si = None            # SearchIndex (shared)
bdag = None          # BDAG LexiconModule
bdb = None           # BDB Complete LexiconModule
halot = None         # HALOT LexiconModule

_results = []        # Collect (test_number, name, passed, detail)


# ═══════════════════════════════════════════════════════════════════════════
# Setup / teardown
# ═══════════════════════════════════════════════════════════════════════════

def setup():
    """Initialize all modules once."""
    global mods, lex, com_mods, si, bdag, bdb, halot
    print("=" * 70)
    print("ACCORDANCE PORT — COMPREHENSIVE SEARCH TEST SUITE (20 cases)")
    print("=" * 70)
    print()
    print("[SETUP] Discovering modules...")
    mods = discover_modules()
    lex = {k: v for k, v in mods.items() if v["type"] in (3, 4)}
    com_mods = {k: v for k, v in mods.items() if v["type"] == 5}
    print(f"[SETUP]   {len(mods)} total, {len(lex)} lexicons, {len(com_mods)} commentaries")

    print("[SETUP] Initializing SearchIndex...")
    si = SearchIndex()
    print(f"[SETUP]   Search index: {si.ensure_index():,} rows")

    print("[SETUP] Loading BDAG...")
    bdag = LexiconModule(lex["BDAG"]["path"])
    print(f"[SETUP]   BDAG: {len(bdag._entries):,} entries")

    print("[SETUP] Loading BDB Complete...")
    bdb = LexiconModule(lex["BDB Complete"]["path"])
    print(f"[SETUP]   BDB: {len(bdb._entries):,} entries")

    print("[SETUP] Loading HALOT...")
    halot = LexiconModule(lex["HALOT"]["path"])
    print(f"[SETUP]   HALOT: {len(halot._entries):,} entries")

    print("[SETUP] Done.\n")


def teardown():
    """Close all modules."""
    for obj, name in [(si, "SearchIndex"), (bdag, "BDAG"), (bdb, "BDB"), (halot, "HALOT")]:
        if obj:
            try:
                obj.close()
            except Exception:
                pass


def record(test_num: int, name: str, passed: bool, detail: str = ""):
    """Record a test result."""
    status = "PASS" if passed else "FAIL"
    print(f"  [{status}]")
    if not passed and detail:
        print(f"         Reason: {detail}")
    _results.append((test_num, name, passed, detail))


# ═══════════════════════════════════════════════════════════════════════════
# Test Cases
# ═══════════════════════════════════════════════════════════════════════════

def test_01_greek_word_study():
    """Test 1: Greek Word Study (Native Script) — λόγος in John 1:1"""
    name = "Greek Word Study — λόγος in John 1:1"
    print(f"\n{'─' * 60}\nTest 1: {name}")
    try:
        # KJV John 1:1 → get Strong's
        vr = get_verse("John 1:1", translation="kjv")
        assert vr is not None, "get_verse returned None"
        assert "G3056" in vr.strongs, f"G3056 not in strongs: {vr.strongs}"
        print(f"  John 1:1 text: {vr.text}")

        # Lookup λόγος in BDAG (BDAG uses Greek Unicode headwords, not Strong's)
        entry = bdag.lookup("λόγος")
        assert entry is not None, "BDAG lookup('λόγος') returned None"
        assert "word" in entry.lower() or "λόγος" in entry, \
            f"Entry lacks 'word' or 'λόγος': {entry[:100]}"
        record(1, name, True)
    except AssertionError as e:
        record(1, name, False, str(e))
    except Exception as e:
        record(1, name, False, f"{type(e).__name__}: {e}")


def test_02_greek_transliterated():
    """Test 2: Greek Transliterated — 'agape'"""
    name = "Greek Transliterated — 'agape'"
    print(f"\n{'─' * 60}\nTest 2: {name}")
    try:
        results = si.search_transliterated("agape", "lexicon", "en", 5)
        assert len(results) > 0, "No transliterated results for agape"
        for r in results[:3]:
            print(f"  {r[1]}: {r[2][:50]}")
        record(2, name, True)
    except AssertionError as e:
        record(2, name, False, str(e))
    except Exception as e:
        record(2, name, False, f"{type(e).__name__}: {e}")


def test_03_hebrew_word_study():
    """Test 3: Hebrew Word Study (Native Script) — חֶסֶד"""
    name = "Hebrew Word Study — חֶסֶד"
    print(f"\n{'─' * 60}\nTest 3: {name}")
    try:
        # Verify H2617 in BDB
        entry = bdb.lookup("H2617")
        assert entry is not None, "H2617 not found in BDB"
        assert len(entry) > 100, f"BDB H2617 entry too short ({len(entry)} chars)"
        print(f"  BDB H2617: found ({len(entry)} chars) ✓")

        # Verify chesed appears in Bible text
        r = si.search("חסד", "bible", "he", 5, fuzzy=True)
        assert len(r) > 0, "חסד not found in Bible text"
        print(f"  חסד in Bible: {len(r)} results ✓")

        # Also verify transliterated "chesed" finds it (non-fatal)
        r2 = si.search_transliterated("chesed", "lexicon", "he", 3)
        print(f"  Transliterated chesed: {len(r2)} results")
        # Non-fatal: transliteration may or may not find lexicon entries

        record(3, name, True)
    except AssertionError as e:
        record(3, name, False, str(e))
    except Exception as e:
        record(3, name, False, f"{type(e).__name__}: {e}")


def test_04_hebrew_transliterated():
    """Test 4: Hebrew Transliterated — forward + reverse"""
    name = "Hebrew Transliterated — forward + reverse"
    print(f"\n{'─' * 60}\nTest 4: {name}")
    try:
        # Test forward transliteration
        assert transliterate_hebrew("בראשית") == "brʾšyt", \
            f'Forward fail: {transliterate_hebrew("בראשית")}'
        print("  Forward transliteration: בראשית → brʾšyt ✓")

        # Test reverse transliteration for words that map cleanly
        r = si.search_transliterated("shalom", "all", "he", 5)
        print(f"  shalom: {len(r)} results")
        assert len(r) > 0, "Transliterated shalom found nothing"

        r = si.search_transliterated("elohim", "all", "he", 5)
        print(f"  elohim: {len(r)} results")

        # Verify that transliterated searches can find Hebrew content
        # (at least one of shalom/elohim/chesed/torah should return results)
        found_any = False
        for word in ["shalom", "elohim", "chesed", "torah"]:
            r = si.search_transliterated(word, "all", "he", 5)
            if len(r) > 0:
                print(f"  ✓ {word}: {len(r)} results ({r[0][1]}:{r[0][2][:50]})")
                found_any = True
        assert found_any, "No transliterated Hebrew searches returned results"

        record(4, name, True)
    except AssertionError as e:
        record(4, name, False, str(e))
    except Exception as e:
        record(4, name, False, f"{type(e).__name__}: {e}")


def test_05_commentary_search():
    """Test 5: Commentary search — 'justification by faith'"""
    name = "Commentary search — 'justification by faith'"
    print(f"\n{'─' * 60}\nTest 5: {name}")
    try:
        results = si.search("justification by faith", "commentary", "en", 10)
        assert len(results) > 0, "No commentary results"
        sources = [r[1] for r in results]
        print(f"  Found {len(results)} results from sources: {sources[:5]}")
        record(5, name, True)
    except AssertionError as e:
        record(5, name, False, str(e))
    except Exception as e:
        record(5, name, False, f"{type(e).__name__}: {e}")


def test_06_multi_source_romans():
    """Test 6: Multi-Source Report — Romans 8:28"""
    name = "Multi-Source Report — Romans 8:28"
    print(f"\n{'─' * 60}\nTest 6: {name}")
    try:
        vr = get_verse("Romans 8:28", translation="kjv")
        assert vr is not None, "get_verse returned None"
        assert len(vr.strongs) > 0, "No Strong's numbers found"
        print(f"  Romans 8:28 strongs: {vr.strongs}")

        # Look up related Greek words in BDAG (BDAG uses Unicode headwords, not Strong's).
        # G4903 = συνεργέω, G18 = ἀγαθός, G4286 = πρόθεσις (strip accents for find())
        found_any = False
        for term in ["συνεργεω", "αγαθος", "προθεσις"]:
            matches = bdag.find(term, 5)
            if matches:
                print(f"  BDAG find({term}): {len(matches)} matches, e.g. {matches[:3]}")
                found_any = True
            else:
                print(f"  BDAG find({term}): no matches")
        assert found_any, "None of the Greek word variants found in BDAG"
        record(6, name, True)
    except AssertionError as e:
        record(6, name, False, str(e))
    except Exception as e:
        record(6, name, False, f"{type(e).__name__}: {e}")


def test_07_cross_reference_covenant():
    """Test 7: Cross-Reference — 'covenant' (בְּרִית)"""
    name = "Cross-Reference — covenant (בְּרִית)"
    print(f"\n{'─' * 60}\nTest 7: {name}")
    try:
        # Search for ברית in Bible
        results = si.search("ברית", "bible", "he", 10, fuzzy=True)
        assert len(results) > 0, "No results for ברית in Bible"
        print(f"  Found {len(results)} Bible results for ברית")

        # Study H1285
        entry = bdb.lookup("H1285")
        assert entry is not None, "H1285 not found in BDB"
        print(f"  BDB H1285: {entry[:100]}")
        record(7, name, True)
    except AssertionError as e:
        record(7, name, False, str(e))
    except Exception as e:
        record(7, name, False, f"{type(e).__name__}: {e}")


def test_08_strongs_chain_pisteuo():
    """Test 8: Strong's Chain — πιστεύω from John 3:16"""
    name = "Strong's Chain — πιστεύω from John 3:16"
    print(f"\n{'─' * 60}\nTest 8: {name}")
    try:
        vr = get_verse("John 3:16", translation="kjv")
        assert vr is not None, "get_verse returned None"
        assert "G4100" in vr.strongs, f"G4100 (πιστεύω) not in strongs: {vr.strongs}"
        print("  John 3:16 has G4100 (πιστεύω) ✓")

        entry = bdag.lookup("πιστεύω")
        assert entry is not None, "BDAG lookup('πιστεύω') returned None"
        assert "believe" in entry.lower() or "πιστεύω" in entry, \
            f"Entry lacks 'believe' or 'πιστεύω': {entry[:100]}"
        record(8, name, True)
    except AssertionError as e:
        record(8, name, False, str(e))
    except Exception as e:
        record(8, name, False, f"{type(e).__name__}: {e}")


def test_09_original_language_phil():
    """Test 9: Original Language — Philippians 2:6-7"""
    name = "Original Language — Philippians 2:6-7"
    print(f"\n{'─' * 60}\nTest 9: {name}")
    try:
        for ref in ["Philippians 2:6", "Philippians 2:7"]:
            vr = get_verse(ref, translation="kjv")
            assert vr is not None, f"get_verse({ref}) returned None"
            assert len(vr.strongs) > 0, f"No Strong's for {ref}"
            print(f"  {ref}: {vr.text[:80]}")

        # Look up actual Greek headwords in BDAG (BDAG uses Unicode Greek, not Strong's)
        # G3444 = μορφή, G725 = ἁρπαγμός, G2758 = κενόω
        found = []
        for greek_word in ["μορφή", "ἁρπαγμός", "κενόω"]:
            entry = bdag.lookup(greek_word)
            if entry:
                found.append(greek_word)
                print(f"  BDAG '{greek_word}': found")
            else:
                print(f"  BDAG '{greek_word}': NOT found")
        assert len(found) > 0, \
            "None of μορφή, ἁρπαγμός, κενόω found in BDAG"
        record(9, name, True)
    except AssertionError as e:
        record(9, name, False, str(e))
    except Exception as e:
        record(9, name, False, f"{type(e).__name__}: {e}")


def test_10_commentary_comparison():
    """Test 10: Commentary Comparison — Matthew 5:3-5"""
    name = "Commentary Comparison — Matthew 5:3-5"
    print(f"\n{'─' * 60}\nTest 10: {name}")
    try:
        any_tested = False
        for abbr in ["Hermeneia NT-21", "JFB"]:
            if abbr in com_mods:
                any_tested = True
                print(f"  Testing {abbr}...")
                cm = CommentaryModule(com_mods[abbr]["path"])
                text = cm.get_section("Matthew 5:3")
                assert text is not None, f"{abbr} get_section returned None"
                assert not text.startswith("[No commentary"), \
                    f"{abbr}: commentary not found — {text[:100]}"
                print(f"  {abbr}: {text[:120]}")
                cm.close()
            else:
                print(f"  {abbr}: module not found (skipping)")
        assert any_tested, "Neither Hermeneia NT-21 nor JFB found"
        record(10, name, True)
    except AssertionError as e:
        record(10, name, False, str(e))
    except Exception as e:
        record(10, name, False, f"{type(e).__name__}: {e}")


def test_11_bible_phrase_search():
    """Test 11: Bible Phrase Search — 'kingdom of heaven'"""
    name = "Bible Phrase Search — 'kingdom of heaven'"
    print(f"\n{'─' * 60}\nTest 11: {name}")
    try:
        results = si.search("kingdom of heaven", "bible", "en", 10)
        assert len(results) > 0, "No Bible results"
        print(f"  Found {len(results)} results:")
        for r in results[:3]:
            print(f"    {r[1]}: {r[2][:50]}")
        record(11, name, True)
    except AssertionError as e:
        record(11, name, False, str(e))
    except Exception as e:
        record(11, name, False, f"{type(e).__name__}: {e}")


def test_12_transliteration_variant():
    """Test 12: Transliteration Variant — 'elohim' vs 'elohiym'"""
    name = "Transliteration Variant — 'elohim' vs 'elohiym'"
    print(f"\n{'─' * 60}\nTest 12: {name}")
    try:
        r1 = si.search_transliterated("elohim", "lexicon", "he", 3)
        r2 = si.search_transliterated("elohiym", "lexicon", "he", 3)
        print(f"  elohim: {len(r1)} results, elohiym: {len(r2)} results")
        assert len(r1) > 0 or len(r2) > 0, \
            f"Neither elohim ({len(r1)}) nor elohiym ({len(r2)}) returned results"
        record(12, name, True)
    except AssertionError as e:
        record(12, name, False, str(e))
    except Exception as e:
        record(12, name, False, f"{type(e).__name__}: {e}")


def test_13_short_greek_query():
    """Test 13: Short Greek Query — δέ"""
    name = "Short Greek Query — δέ"
    print(f"\n{'─' * 60}\nTest 13: {name}")
    try:
        entry = bdag.lookup("δέ")
        assert entry is not None, "BDAG lookup('δέ') returned None"
        assert len(entry) > 20, f"Entry too short: {len(entry)} chars"
        print(f"  Entry length: {len(entry)} chars")
        print(f"  Preview: {entry[:120]}")
        record(13, name, True)
    except AssertionError as e:
        record(13, name, False, str(e))
    except Exception as e:
        record(13, name, False, f"{type(e).__name__}: {e}")


def test_14_compound_word_prefix():
    """Test 14: Compound Word — θεο- prefix"""
    name = "Compound Word — θεο- prefix"
    print(f"\n{'─' * 60}\nTest 14: {name}")
    try:
        matches = bdag.find("θεο", 10)
        assert len(matches) > 0, "No Greek words starting with θεο found"
        print(f"  Found {len(matches)} matches: {matches}")
        record(14, name, True)
    except AssertionError as e:
        record(14, name, False, str(e))
    except Exception as e:
        record(14, name, False, f"{type(e).__name__}: {e}")


def test_15_diacritic_normalization():
    """Test 15: Diacritic Normalization — shalom"""
    name = "Diacritic Normalization — shalom"
    print(f"\n{'─' * 60}\nTest 15: {name}")
    try:
        r1 = si.search("שָׁלוֹם", "bible", "he", 5, fuzzy=True)
        r2 = si.search("שלום", "bible", "he", 5, fuzzy=True)
        r3 = si.search_transliterated("shalom", "bible", "he", 5)
        print(f"  Diacritic: {len(r1)}, Plain: {len(r2)}, Translit: {len(r3)}")
        assert len(r1) > 0 or len(r2) > 0 or len(r3) > 0, \
            "All three shalom variants returned 0 results"
        record(15, name, True)
    except AssertionError as e:
        record(15, name, False, str(e))
    except Exception as e:
        record(15, name, False, f"{type(e).__name__}: {e}")


def test_16_translation_comparison_isaiah():
    """Test 16: Translation Comparison — Isaiah 7:14"""
    name = "Translation Comparison — Isaiah 7:14"
    print(f"\n{'─' * 60}\nTest 16: {name}")
    try:
        vr = get_verse("Isaiah 7:14", translation="kjv")
        assert vr is not None, "get_verse returned None"
        assert "." in vr.text, f"No text received: {vr.text}"
        print(f"  Isaiah 7:14: {vr.text}")
        print(f"  Strong's: {vr.strongs}")

        # Look up H5959 (עַלְמָה) via BDB — also try the Hebrew headword directly
        found_almah = False
        # Try Strong's-based lookup first
        for sn in vr.strongs:
            entry = bdb.lookup(sn)
            if entry and "almah" in entry.lower():
                print(f"  {sn} (almah) found: {entry[:100]}")
                found_almah = True
                break
        # Fallback: try direct Hebrew headword lookup
        if not found_almah:
            entry = bdb.lookup("עַלְמָה")
            if entry:
                print(f"  BDB lookup('עַלְמָה'): found ({entry[:100]})")
                found_almah = True
        if not found_almah:
            print("  NOTE: No almah entry found via Strong's or headword lookup")
        # Test passes if verse text was retrieved
        record(16, name, True)
    except AssertionError as e:
        record(16, name, False, str(e))
    except Exception as e:
        record(16, name, False, f"{type(e).__name__}: {e}")


def test_17_book_outline():
    """Test 17: Book Outline — Ruth"""
    name = "Book Outline — Ruth"
    print(f"\n{'─' * 60}\nTest 17: {name}")
    try:
        results = si.search("Ruth", "bible", "en", 5, book="Ruth")
        assert len(results) > 0, "No Ruth results — book filter may not be working"
        print(f"  Found {len(results)} Ruth results (book column populated — filter working)")
        for r in results[:3]:
            print(f"    {r[1]}: {r[2][:50]}")
        record(17, name, True)
    except AssertionError as e:
        record(17, name, False, str(e))
    except Exception as e:
        record(17, name, False, f"{type(e).__name__}: {e}")


def test_18_lexicon_content_covenant():
    """Test 18: Lexicon Content Search — 'covenant' in definitions"""
    name = "Lexicon Content Search — 'covenant' in definitions"
    print(f"\n{'─' * 60}\nTest 18: {name}")
    try:
        results = si.search("covenant", "lexicon", "en", 10)
        assert len(results) > 0, "No lexicon entries mention covenant"
        print(f"  Found {len(results)} results:")
        for r in results[:5]:
            print(f"    {r[1]}: {r[2][:60]}")
        record(18, name, True)
    except AssertionError as e:
        record(18, name, False, str(e))
    except Exception as e:
        record(18, name, False, f"{type(e).__name__}: {e}")


def test_19_commentary_book_coverage():
    """Test 19: Commentary Book Coverage — Hermeneia OT-19 Psalms"""
    name = "Commentary Book Coverage — Hermeneia OT-19 Psalms"
    print(f"\n{'─' * 60}\nTest 19: {name}")
    try:
        assert "Hermeneia OT-19" in com_mods, \
            "Hermeneia OT-19 not found in discovered modules"
        cm = CommentaryModule(com_mods["Hermeneia OT-19"]["path"])
        books = cm.list_books()
        book_names = [b for b, _ in books]
        assert "Psalms" in book_names, \
            f"Psalms not in Hermeneia OT-19. Books: {book_names[:10]}"
        print(f"  Hermeneia OT-19 covers {len(books)} books: {book_names}")

        text = cm.get_section("Psalms 23:1")
        assert text is not None, "get_section returned None"
        assert not text.startswith("[No commentary"), \
            f"No commentary found: {text[:100]}"
        print(f"  Psalms 23:1: {text[:120]}")
        cm.close()
        record(19, name, True)
    except AssertionError as e:
        record(19, name, False, str(e))
    except Exception as e:
        record(19, name, False, f"{type(e).__name__}: {e}")


def test_20_full_pipeline_grace():
    """Test 20: Full Pipeline — 'grace'"""
    name = "Full Pipeline — 'grace'"
    print(f"\n{'─' * 60}\nTest 20: {name}")
    try:
        # Search for grace in lexicons
        results = si.search("grace", "lexicon", "en", 5)
        assert len(results) > 0, "No lexicon search results for grace"
        print(f"  Lexicon search: {len(results)} results")
        for r in results[:3]:
            print(f"    {r[1]}: {r[2][:60]}")

        # Get χάρις definition (BDAG uses Greek Unicode headwords)
        entry_g = bdag.lookup("χάρις")
        assert entry_g is not None, "BDAG lookup('χάρις') returned None"
        assert "grace" in entry_g.lower() or "χάρις" in entry_g, \
            f"BDAG χάρις lacks 'grace'/'χάρις': {entry_g[:100]}"
        print("  BDAG χάρις: OK")

        # Get חֵן (H2580) definition
        entry_h = bdb.lookup("H2580")
        assert entry_h is not None, "BDB lookup('H2580') returned None"
        print("  BDB H2580 (חֵן): OK")

        # Commentary on Ephesians 2:8
        assert "JFB" in com_mods, "JFB not found in discovered modules"
        cm = CommentaryModule(com_mods["JFB"]["path"])
        text = cm.get_section("Ephesians 2:8")
        assert text is not None, "JFB get_section returned None"
        assert not text.startswith("[No commentary"), \
            f"JFB no commentary: {text[:100]}"
        print(f"  JFB Ephesians 2:8: {text[:120]}")
        cm.close()
        record(20, name, True)
    except AssertionError as e:
        record(20, name, False, str(e))
    except Exception as e:
        record(20, name, False, f"{type(e).__name__}: {e}")


# ═══════════════════════════════════════════════════════════════════════════
# Main runner
# ═══════════════════════════════════════════════════════════════════════════

def run_all():
    """Run all 20 test cases and print summary."""
    tests = [
        test_01_greek_word_study,
        test_02_greek_transliterated,
        test_03_hebrew_word_study,
        test_04_hebrew_transliterated,
        test_05_commentary_search,
        test_06_multi_source_romans,
        test_07_cross_reference_covenant,
        test_08_strongs_chain_pisteuo,
        test_09_original_language_phil,
        test_10_commentary_comparison,
        test_11_bible_phrase_search,
        test_12_transliteration_variant,
        test_13_short_greek_query,
        test_14_compound_word_prefix,
        test_15_diacritic_normalization,
        test_16_translation_comparison_isaiah,
        test_17_book_outline,
        test_18_lexicon_content_covenant,
        test_19_commentary_book_coverage,
        test_20_full_pipeline_grace,
    ]

    for test_func in tests:
        try:
            test_func()
        except Exception as e:
            import traceback  # noqa: F401
            # Find the test number from the function name
            num = test_func.__name__.split("_")[1] if "_" in test_func.__name__ else "?"
            print(f"\n  [ERROR] Unhandled exception in {test_func.__name__}:")
            print(f"    {type(e).__name__}: {e}")

    # ── Summary ──
    passed = [r for r in _results if r[2]]
    failed = [r for r in _results if not r[2]]

    print("\n")
    print("=" * 70)
    print("TEST SUMMARY")
    print("=" * 70)
    print()
    print(f"  Total tests:    {len(_results)}")
    print(f"  Passed:         {len(passed)}")
    print(f"  Failed:         {len(failed)}")
    print("  Errors:         0")  # errors are counted as failures with detail
    print()

    if failed:
        print("─" * 70)
        print("FAILURES:")
        print("─" * 70)
        for num, name, _, detail in failed:
            print(f"  Test {num:2d}: {name}")
            print(f"         {detail}")
        print()

    if passed:
        print("─" * 70)
        print(f"PASSED ({len(passed)}/{len(_results)}):")
        print("─" * 70)
        for num, name, _, _ in passed:
            print(f"  Test {num:2d}: {name}")

    print()
    print("=" * 70)

    return len(failed) == 0


if __name__ == "__main__":
    setup()
    try:
        success = run_all()
    finally:
        teardown()
    sys.exit(0 if success else 1)
