@@ -577,6 +577,38 @@ final class IORingTests: XCTestCase {
577577 XCTFail ( " expected POLLERR, got 0x \( String ( result, radix: 16 ) ) " )
578578 }
579579 }
580+
581+ // A completion's `result` is two things in one field: a non-negative
582+ // value is an event mask, and a negative value is a negated errno. The
583+ // sign is the only thing distinguishing them, so it has to be checked
584+ // before the value is treated as anything else.
585+ func testPollAddOnInvalidDescriptor( ) throws {
586+ try XCTSkipIf ( !uringEnabled, failureMessage)
587+ var ring = try IORing ( queueDepth: 8 )
588+
589+ let request = IORing . Request. pollAdd (
590+ FileDescriptor ( rawValue: - 1 ) , pollEvents: . pollIn,
591+ isMultiShot: false , context: 99
592+ )
593+ let success = try ring. submit ( linkedRequests: request)
594+ XCTAssertEqual ( success, true )
595+
596+ guard let completion = ring. tryConsumeCompletion ( ) else {
597+ XCTFail ( " expected a completion for the failed poll " )
598+ return
599+ }
600+ XCTAssertEqual ( completion. context, 99 )
601+
602+ // A negative value for `result` marks the completion a a failure.
603+ XCTAssertLessThan ( completion. result, 0 , " expected a failure " )
604+ // The negative value is the error code multiplied by -1.
605+ XCTAssertEqual ( Errno ( rawValue: - completion. result) , . badFileDescriptor)
606+
607+ // A negative result may look like another result code.
608+ // Checking for the error must happen first.
609+ let pollNval = Int32 ( IORing . Request. PollEvents. pollNval. rawValue)
610+ XCTAssertNotEqual ( completion. result & pollNval, 0 )
611+ }
580612}
581613#endif // os(Linux)
582614#endif // compiler(>=6.2) && $Lifetimes
0 commit comments