| # RUN: %PYTHON %s | FileCheck %s | |
| import gc | |
| from mlir.ir import * | |
| from mlir._mlir_libs._mlirPythonTestNanobind import ( | |
| test_diagnostics_with_errors_and_notes, | |
| ) | |
| def run(f): | |
| print("\nTEST:", f.__name__) | |
| f() | |
| gc.collect() | |
| assert Context._get_live_count() == 0 | |
| return f | |
| @run | |
| def testLifecycleContextDestroy(): | |
| ctx = Context() | |
| def callback(foo): | |
| ... | |
| handler = ctx.attach_diagnostic_handler(callback) | |
| assert handler.attached | |
| # If context is destroyed before the handler, it should auto-detach. | |
| ctx = None | |
| gc.collect() | |
| assert not handler.attached | |
| # And finally collecting the handler should be fine. | |
| handler = None | |
| gc.collect() | |
| @run | |
| def testLifecycleExplicitDetach(): | |
| ctx = Context() | |
| def callback(foo): | |
| ... | |
| handler = ctx.attach_diagnostic_handler(callback) | |
| assert handler.attached | |
| handler.detach() | |
| assert not handler.attached | |
| @run | |
| def testLifecycleWith(): | |
| ctx = Context() | |
| def callback(foo): | |
| ... | |
| with ctx.attach_diagnostic_handler(callback) as handler: | |
| assert handler.attached | |
| assert not handler.attached | |
| @run | |
| def testLifecycleWithAndExplicitDetach(): | |
| ctx = Context() | |
| def callback(foo): | |
| ... | |
| with ctx.attach_diagnostic_handler(callback) as handler: | |
| assert handler.attached | |
| handler.detach() | |
| assert not handler.attached | |
| # CHECK-LABEL: TEST: testDiagnosticCallback | |
| @run | |
| def testDiagnosticCallback(): | |
| ctx = Context() | |
| def callback(d): | |
| # CHECK: DIAGNOSTIC: message='foobar', severity=DiagnosticSeverity.ERROR, loc=loc(unknown) | |
| print( | |
| f"DIAGNOSTIC: message='{d.message}', severity={d.severity}, loc={d.location}" | |
| ) | |
| return True | |
| handler = ctx.attach_diagnostic_handler(callback) | |
| loc = Location.unknown(ctx) | |
| loc.emit_error("foobar") | |
| assert not handler.had_error | |
| # CHECK-LABEL: TEST: testDiagnosticEmptyNotes | |
| # TODO: Come up with a way to inject a diagnostic with notes from this API. | |
| @run | |
| def testDiagnosticEmptyNotes(): | |
| ctx = Context() | |
| def callback(d): | |
| # CHECK: DIAGNOSTIC: notes=() | |
| print(f"DIAGNOSTIC: notes={d.notes}") | |
| return True | |
| handler = ctx.attach_diagnostic_handler(callback) | |
| loc = Location.unknown(ctx) | |
| loc.emit_error("foobar") | |
| assert not handler.had_error | |
| # CHECK-LABEL: TEST: testDiagnosticNonEmptyNotes | |
| @run | |
| def testDiagnosticNonEmptyNotes(): | |
| ctx = Context() | |
| ctx.emit_error_diagnostics = True | |
| def callback(d): | |
| # CHECK: DIAGNOSTIC: | |
| # CHECK: message='arith.addi' op requires one result | |
| # CHECK: notes=['see current operation: "arith.addi"() {{.*}} : () -> ()'] | |
| print(f"DIAGNOSTIC:") | |
| print(f" message={d.message}") | |
| print(f" notes={list(map(str, d.notes))}") | |
| return True | |
| handler = ctx.attach_diagnostic_handler(callback) | |
| loc = Location.unknown(ctx) | |
| try: | |
| Operation.create("arith.addi", loc=loc).verify() | |
| except MLIRError: | |
| pass | |
| assert not handler.had_error | |
| # CHECK-LABEL: TEST: testDiagnosticCallbackException | |
| @run | |
| def testDiagnosticCallbackException(): | |
| ctx = Context() | |
| def callback(d): | |
| raise ValueError("Error in handler") | |
| handler = ctx.attach_diagnostic_handler(callback) | |
| loc = Location.unknown(ctx) | |
| loc.emit_error("foobar") | |
| assert handler.had_error | |
| # CHECK-LABEL: TEST: testEscapingDiagnostic | |
| @run | |
| def testEscapingDiagnostic(): | |
| ctx = Context() | |
| diags = [] | |
| def callback(d): | |
| diags.append(d) | |
| return True | |
| handler = ctx.attach_diagnostic_handler(callback) | |
| loc = Location.unknown(ctx) | |
| loc.emit_error("foobar") | |
| assert not handler.had_error | |
| # CHECK: DIAGNOSTIC: <Invalid Diagnostic> | |
| print(f"DIAGNOSTIC: {str(diags[0])}") | |
| try: | |
| diags[0].severity | |
| raise RuntimeError("expected exception") | |
| except ValueError: | |
| pass | |
| try: | |
| diags[0].location | |
| raise RuntimeError("expected exception") | |
| except ValueError: | |
| pass | |
| try: | |
| diags[0].message | |
| raise RuntimeError("expected exception") | |
| except ValueError: | |
| pass | |
| try: | |
| diags[0].notes | |
| raise RuntimeError("expected exception") | |
| except ValueError: | |
| pass | |
| # CHECK-LABEL: TEST: testDiagnosticReturnTrueHandles | |
| @run | |
| def testDiagnosticReturnTrueHandles(): | |
| ctx = Context() | |
| def callback1(d): | |
| print(f"CALLBACK1: {d}") | |
| return True | |
| def callback2(d): | |
| print(f"CALLBACK2: {d}") | |
| return True | |
| ctx.attach_diagnostic_handler(callback1) | |
| ctx.attach_diagnostic_handler(callback2) | |
| loc = Location.unknown(ctx) | |
| # CHECK-NOT: CALLBACK1 | |
| # CHECK: CALLBACK2: foobar | |
| # CHECK-NOT: CALLBACK1 | |
| loc.emit_error("foobar") | |
| # CHECK-LABEL: TEST: testDiagnosticReturnFalseDoesNotHandle | |
| @run | |
| def testDiagnosticReturnFalseDoesNotHandle(): | |
| ctx = Context() | |
| def callback1(d): | |
| print(f"CALLBACK1: {d}") | |
| return True | |
| def callback2(d): | |
| print(f"CALLBACK2: {d}") | |
| return False | |
| ctx.attach_diagnostic_handler(callback1) | |
| ctx.attach_diagnostic_handler(callback2) | |
| loc = Location.unknown(ctx) | |
| # CHECK: CALLBACK2: foobar | |
| # CHECK: CALLBACK1: foobar | |
| loc.emit_error("foobar") | |
| # CHECK-LABEL: TEST: testBuiltInDiagnosticsHandler | |
| @run | |
| def testBuiltInDiagnosticsHandler(): | |
| ctx = Context() | |
| try: | |
| test_diagnostics_with_errors_and_notes(ctx) | |
| except ValueError as e: | |
| # CHECK: created error | |
| # CHECK: attached note | |
| print(e) |