diff --git a/packages/camera/camera_avfoundation/example/ios/Runner.xcodeproj/project.pbxproj b/packages/camera/camera_avfoundation/example/ios/Runner.xcodeproj/project.pbxproj index 8d0c4d0b0d21..575ae7828f20 100644 --- a/packages/camera/camera_avfoundation/example/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/camera/camera_avfoundation/example/ios/Runner.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 54; + objectVersion = 60; objects = { /* Begin PBXBuildFile section */ @@ -11,9 +11,7 @@ 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 54D650172516862D30686934 /* libPods-Runner.a in Frameworks */ = {isa = PBXBuildFile; fileRef = ECAF63F924EFA2D68883BA85 /* libPods-Runner.a */; }; - 788A065A27B0E02900533D74 /* StreamingTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 788A065927B0E02900533D74 /* StreamingTest.m */; }; 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; }; - 7F29EB222D269ED500740257 /* MockEventChannel.m in Sources */ = {isa = PBXBuildFile; fileRef = 7F29EB212D269ED500740257 /* MockEventChannel.m */; }; 7F29EB292D26A59000740257 /* MockCameraDeviceDiscoverer.m in Sources */ = {isa = PBXBuildFile; fileRef = 7F29EB282D26A59000740257 /* MockCameraDeviceDiscoverer.m */; }; 7F29EB412D281C7E00740257 /* MockCaptureSession.m in Sources */ = {isa = PBXBuildFile; fileRef = 7F29EB402D281C7E00740257 /* MockCaptureSession.m */; }; 7F8FD2292D4BFABF001AF2C1 /* MockGlobalEventApi.m in Sources */ = {isa = PBXBuildFile; fileRef = 7F8FD2282D4BFABF001AF2C1 /* MockGlobalEventApi.m */; }; @@ -21,18 +19,23 @@ 7F8FD22F2D4D0B88001AF2C1 /* MockFlutterBinaryMessenger.m in Sources */ = {isa = PBXBuildFile; fileRef = 7F8FD22E2D4D0B88001AF2C1 /* MockFlutterBinaryMessenger.m */; }; 7FCEDD352D43C2B900EA1CA8 /* MockDeviceOrientationProvider.m in Sources */ = {isa = PBXBuildFile; fileRef = 7FCEDD342D43C2B900EA1CA8 /* MockDeviceOrientationProvider.m */; }; 7FCEDD362D43C2B900EA1CA8 /* MockCaptureDevice.m in Sources */ = {isa = PBXBuildFile; fileRef = 7FCEDD322D43C2B900EA1CA8 /* MockCaptureDevice.m */; }; - 7FD582122D579650003B1200 /* MockWritableData.m in Sources */ = {isa = PBXBuildFile; fileRef = 7FD582112D579650003B1200 /* MockWritableData.m */; }; 7FD582202D579ECC003B1200 /* MockCapturePhotoOutput.m in Sources */ = {isa = PBXBuildFile; fileRef = 7FD5821F2D579ECC003B1200 /* MockCapturePhotoOutput.m */; }; 7FD582272D57C020003B1200 /* MockAssetWriter.m in Sources */ = {isa = PBXBuildFile; fileRef = 7FD582262D57C020003B1200 /* MockAssetWriter.m */; }; 7FD582352D57D97C003B1200 /* MockCaptureDeviceFormat.m in Sources */ = {isa = PBXBuildFile; fileRef = 7FD582342D57D97C003B1200 /* MockCaptureDeviceFormat.m */; }; 7FD83D2B2D5BA65B00F4DB7C /* MockCaptureConnection.m in Sources */ = {isa = PBXBuildFile; fileRef = 7FD83D2A2D5BA65B00F4DB7C /* MockCaptureConnection.m */; }; + 970ADABE2D6740A900EFDCD9 /* MockWritableData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 970ADABD2D6740A900EFDCD9 /* MockWritableData.swift */; }; + 970ADAC02D6764CC00EFDCD9 /* MockEventChannel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 970ADABF2D6764CC00EFDCD9 /* MockEventChannel.swift */; }; 972CA92B2D5A1D8C004B846F /* CameraPropertiesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 972CA92A2D5A1D8C004B846F /* CameraPropertiesTests.swift */; }; 972CA92D2D5A28C4004B846F /* QueueUtilsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 972CA92C2D5A28C4004B846F /* QueueUtilsTests.swift */; }; 972CA9312D5A366C004B846F /* CameraExposureTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 972CA9302D5A366C004B846F /* CameraExposureTests.swift */; }; 977A25202D5A439300931E34 /* AvailableCamerasTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 977A251F2D5A439300931E34 /* AvailableCamerasTests.swift */; }; 977A25222D5A49EC00931E34 /* CameraFocusTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 977A25212D5A49EC00931E34 /* CameraFocusTests.swift */; }; 977A25242D5A511600931E34 /* CameraPermissionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 977A25232D5A511600931E34 /* CameraPermissionTests.swift */; }; + 977CAC9F2D5E5180001E5DC3 /* ThreadSafeEventChannelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 977CAC9E2D5E5180001E5DC3 /* ThreadSafeEventChannelTests.swift */; }; + 978296CF2D5F744B0009BDD3 /* PhotoCaptureTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 978296CE2D5F744B0009BDD3 /* PhotoCaptureTests.swift */; }; 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */; }; + 978D90B42D5F630300CD817E /* StreamingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 978D90B32D5F630300CD817E /* StreamingTests.swift */; }; + 97922B0D2D6380C300A9B4CF /* SampleBufferTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97922B0C2D6380C300A9B4CF /* SampleBufferTests.swift */; }; 979B3DFB2D5B6BC7009BDE1A /* ExceptionCatcher.m in Sources */ = {isa = PBXBuildFile; fileRef = 979B3DFA2D5B6BC7009BDE1A /* ExceptionCatcher.m */; }; 979B3DFE2D5B985B009BDE1A /* CameraCaptureSessionQueueRaceConditionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 979B3DFD2D5B985B009BDE1A /* CameraCaptureSessionQueueRaceConditionTests.swift */; }; 979B3E002D5B9E6C009BDE1A /* CameraMethodChannelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 979B3DFF2D5B9E6C009BDE1A /* CameraMethodChannelTests.swift */; }; @@ -45,9 +48,6 @@ 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; 97DB234D2D566D0700CEFE66 /* CameraPreviewPauseTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97DB234C2D566D0700CEFE66 /* CameraPreviewPauseTests.swift */; }; - E071CF7227B3061B006EF3BA /* FLTCamPhotoCaptureTests.m in Sources */ = {isa = PBXBuildFile; fileRef = E071CF7127B3061B006EF3BA /* FLTCamPhotoCaptureTests.m */; }; - E071CF7427B31DE4006EF3BA /* FLTCamSampleBufferTests.m in Sources */ = {isa = PBXBuildFile; fileRef = E071CF7327B31DE4006EF3BA /* FLTCamSampleBufferTests.m */; }; - E0C6E2022770F01A00EA6AA3 /* ThreadSafeEventChannelTests.m in Sources */ = {isa = PBXBuildFile; fileRef = E0C6E1FF2770F01A00EA6AA3 /* ThreadSafeEventChannelTests.m */; }; E0CDBAC227CD9729002561D9 /* CameraTestUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = E0CDBAC127CD9729002561D9 /* CameraTestUtils.m */; }; /* End PBXBuildFile section */ @@ -81,12 +81,9 @@ 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; 4A191381C3593DF1AC4E7559 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; - 788A065927B0E02900533D74 /* StreamingTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = StreamingTest.m; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; - 7F29EB202D269E4300740257 /* MockEventChannel.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MockEventChannel.h; sourceTree = ""; }; - 7F29EB212D269ED500740257 /* MockEventChannel.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MockEventChannel.m; sourceTree = ""; }; 7F29EB272D26A55300740257 /* MockCameraDeviceDiscoverer.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MockCameraDeviceDiscoverer.h; sourceTree = ""; }; 7F29EB282D26A59000740257 /* MockCameraDeviceDiscoverer.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MockCameraDeviceDiscoverer.m; sourceTree = ""; }; 7F29EB3E2D281C5800740257 /* MockCaptureSession.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MockCaptureSession.h; sourceTree = ""; }; @@ -101,8 +98,6 @@ 7FCEDD322D43C2B900EA1CA8 /* MockCaptureDevice.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MockCaptureDevice.m; sourceTree = ""; }; 7FCEDD332D43C2B900EA1CA8 /* MockDeviceOrientationProvider.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MockDeviceOrientationProvider.h; sourceTree = ""; }; 7FCEDD342D43C2B900EA1CA8 /* MockDeviceOrientationProvider.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MockDeviceOrientationProvider.m; sourceTree = ""; }; - 7FD582112D579650003B1200 /* MockWritableData.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MockWritableData.m; sourceTree = ""; }; - 7FD582132D57965A003B1200 /* MockWritableData.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MockWritableData.h; sourceTree = ""; }; 7FD5821F2D579ECC003B1200 /* MockCapturePhotoOutput.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MockCapturePhotoOutput.m; sourceTree = ""; }; 7FD582212D579ED9003B1200 /* MockCapturePhotoOutput.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MockCapturePhotoOutput.h; sourceTree = ""; }; 7FD582262D57C020003B1200 /* MockAssetWriter.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MockAssetWriter.m; sourceTree = ""; }; @@ -111,8 +106,8 @@ 7FD582362D57D989003B1200 /* MockCaptureDeviceFormat.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MockCaptureDeviceFormat.h; sourceTree = ""; }; 7FD83D292D5BA49100F4DB7C /* MockCaptureConnection.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MockCaptureConnection.h; sourceTree = ""; }; 7FD83D2A2D5BA65B00F4DB7C /* MockCaptureConnection.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MockCaptureConnection.m; sourceTree = ""; }; - 7FD582342D57D97C003B1200 /* MockCaptureDeviceFormat.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MockCaptureDeviceFormat.m; sourceTree = ""; }; - 7FD582362D57D989003B1200 /* MockCaptureDeviceFormat.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MockCaptureDeviceFormat.h; sourceTree = ""; }; + 970ADABD2D6740A900EFDCD9 /* MockWritableData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockWritableData.swift; sourceTree = ""; }; + 970ADABF2D6764CC00EFDCD9 /* MockEventChannel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockEventChannel.swift; sourceTree = ""; }; 972CA92A2D5A1D8C004B846F /* CameraPropertiesTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraPropertiesTests.swift; sourceTree = ""; }; 972CA92C2D5A28C4004B846F /* QueueUtilsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QueueUtilsTests.swift; sourceTree = ""; }; 972CA9302D5A366C004B846F /* CameraExposureTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraExposureTests.swift; sourceTree = ""; }; @@ -121,6 +116,10 @@ 977A251F2D5A439300931E34 /* AvailableCamerasTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AvailableCamerasTests.swift; sourceTree = ""; }; 977A25212D5A49EC00931E34 /* CameraFocusTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraFocusTests.swift; sourceTree = ""; }; 977A25232D5A511600931E34 /* CameraPermissionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraPermissionTests.swift; sourceTree = ""; }; + 977CAC9E2D5E5180001E5DC3 /* ThreadSafeEventChannelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreadSafeEventChannelTests.swift; sourceTree = ""; }; + 978296CE2D5F744B0009BDD3 /* PhotoCaptureTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhotoCaptureTests.swift; sourceTree = ""; }; + 978D90B32D5F630300CD817E /* StreamingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StreamingTests.swift; sourceTree = ""; }; + 97922B0C2D6380C300A9B4CF /* SampleBufferTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SampleBufferTests.swift; sourceTree = ""; }; 979B3DF92D5B6BA2009BDE1A /* ExceptionCatcher.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ExceptionCatcher.h; sourceTree = ""; }; 979B3DFA2D5B6BC7009BDE1A /* ExceptionCatcher.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ExceptionCatcher.m; sourceTree = ""; }; 979B3DFC2D5B985B009BDE1A /* RunnerTests-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "RunnerTests-Bridging-Header.h"; sourceTree = ""; }; @@ -140,9 +139,6 @@ 9DDC4CE84A8B378AE4A8CD9C /* libPods-RunnerTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-RunnerTests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; A8F314CD1C64E9257EBC811D /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; B61D98BBC8FB276D1C4A7BB2 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; - E071CF7127B3061B006EF3BA /* FLTCamPhotoCaptureTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FLTCamPhotoCaptureTests.m; sourceTree = ""; }; - E071CF7327B31DE4006EF3BA /* FLTCamSampleBufferTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FLTCamSampleBufferTests.m; sourceTree = ""; }; - E0C6E1FF2770F01A00EA6AA3 /* ThreadSafeEventChannelTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ThreadSafeEventChannelTests.m; sourceTree = ""; }; E0CDBAC027CD9729002561D9 /* CameraTestUtils.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CameraTestUtils.h; sourceTree = ""; }; E0CDBAC127CD9729002561D9 /* CameraTestUtils.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CameraTestUtils.m; sourceTree = ""; }; E67C6DBF6478BE708993169F /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; @@ -175,12 +171,10 @@ children = ( 7F29EB3F2D281C6D00740257 /* Mocks */, 03BB766C2665316900CE5A93 /* Info.plist */, - E0C6E1FF2770F01A00EA6AA3 /* ThreadSafeEventChannelTests.m */, - E071CF7127B3061B006EF3BA /* FLTCamPhotoCaptureTests.m */, - E071CF7327B31DE4006EF3BA /* FLTCamSampleBufferTests.m */, E0CDBAC027CD9729002561D9 /* CameraTestUtils.h */, E0CDBAC127CD9729002561D9 /* CameraTestUtils.m */, - 788A065927B0E02900533D74 /* StreamingTest.m */, + 979B3DF92D5B6BA2009BDE1A /* ExceptionCatcher.h */, + 979B3DFA2D5B6BC7009BDE1A /* ExceptionCatcher.m */, 979B3DFC2D5B985B009BDE1A /* RunnerTests-Bridging-Header.h */, 979B3DFD2D5B985B009BDE1A /* CameraCaptureSessionQueueRaceConditionTests.swift */, 979B3DFF2D5B9E6C009BDE1A /* CameraMethodChannelTests.swift */, @@ -195,8 +189,10 @@ 977A25212D5A49EC00931E34 /* CameraFocusTests.swift */, 977A25232D5A511600931E34 /* CameraPermissionTests.swift */, 97C0FFAD2D5E023200A36284 /* SavePhotoDelegateTests.swift */, - 979B3DF92D5B6BA2009BDE1A /* ExceptionCatcher.h */, - 979B3DFA2D5B6BC7009BDE1A /* ExceptionCatcher.m */, + 977CAC9E2D5E5180001E5DC3 /* ThreadSafeEventChannelTests.swift */, + 978D90B32D5F630300CD817E /* StreamingTests.swift */, + 97922B0C2D6380C300A9B4CF /* SampleBufferTests.swift */, + 978296CE2D5F744B0009BDD3 /* PhotoCaptureTests.swift */, ); path = RunnerTests; sourceTree = ""; @@ -233,12 +229,10 @@ 7FCEDD342D43C2B900EA1CA8 /* MockDeviceOrientationProvider.m */, 7F29EB282D26A59000740257 /* MockCameraDeviceDiscoverer.m */, 7F29EB272D26A55300740257 /* MockCameraDeviceDiscoverer.h */, - 7F29EB202D269E4300740257 /* MockEventChannel.h */, - 7F29EB212D269ED500740257 /* MockEventChannel.m */, 7F29EB3E2D281C5800740257 /* MockCaptureSession.h */, 7F29EB402D281C7E00740257 /* MockCaptureSession.m */, - 7FD582112D579650003B1200 /* MockWritableData.m */, - 7FD582132D57965A003B1200 /* MockWritableData.h */, + 970ADABD2D6740A900EFDCD9 /* MockWritableData.swift */, + 970ADABF2D6764CC00EFDCD9 /* MockEventChannel.swift */, ); path = Mocks; sourceTree = ""; @@ -527,36 +521,35 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - E071CF7227B3061B006EF3BA /* FLTCamPhotoCaptureTests.m in Sources */, 97BD4A0E2D5CC5AE00F857D5 /* CameraSettingsTests.swift in Sources */, 972CA92D2D5A28C4004B846F /* QueueUtilsTests.swift in Sources */, 979B3DFB2D5B6BC7009BDE1A /* ExceptionCatcher.m in Sources */, - E071CF7227B3061B006EF3BA /* FLTCamPhotoCaptureTests.m in Sources */, 7FD83D2B2D5BA65B00F4DB7C /* MockCaptureConnection.m in Sources */, 977A25242D5A511600931E34 /* CameraPermissionTests.swift in Sources */, + 970ADABE2D6740A900EFDCD9 /* MockWritableData.swift in Sources */, 7F8FD2292D4BFABF001AF2C1 /* MockGlobalEventApi.m in Sources */, - E071CF7427B31DE4006EF3BA /* FLTCamSampleBufferTests.m in Sources */, 7FD582352D57D97C003B1200 /* MockCaptureDeviceFormat.m in Sources */, 979B3DFE2D5B985B009BDE1A /* CameraCaptureSessionQueueRaceConditionTests.swift in Sources */, - 7F29EB222D269ED500740257 /* MockEventChannel.m in Sources */, 7F8FD22F2D4D0B88001AF2C1 /* MockFlutterBinaryMessenger.m in Sources */, + 97922B0D2D6380C300A9B4CF /* SampleBufferTests.swift in Sources */, 972CA92B2D5A1D8C004B846F /* CameraPropertiesTests.swift in Sources */, E0CDBAC227CD9729002561D9 /* CameraTestUtils.m in Sources */, + 978296CF2D5F744B0009BDD3 /* PhotoCaptureTests.swift in Sources */, 7FD582202D579ECC003B1200 /* MockCapturePhotoOutput.m in Sources */, 979B3E002D5B9E6C009BDE1A /* CameraMethodChannelTests.swift in Sources */, 97DB234D2D566D0700CEFE66 /* CameraPreviewPauseTests.swift in Sources */, + 970ADAC02D6764CC00EFDCD9 /* MockEventChannel.swift in Sources */, 977A25202D5A439300931E34 /* AvailableCamerasTests.swift in Sources */, 972CA9312D5A366C004B846F /* CameraExposureTests.swift in Sources */, 7F29EB292D26A59000740257 /* MockCameraDeviceDiscoverer.m in Sources */, 97BD4A102D5CE13500F857D5 /* CameraSessionPresetsTests.swift in Sources */, - 788A065A27B0E02900533D74 /* StreamingTest.m in Sources */, 7FD582272D57C020003B1200 /* MockAssetWriter.m in Sources */, - E0C6E2022770F01A00EA6AA3 /* ThreadSafeEventChannelTests.m in Sources */, 979B3E022D5BA48F009BDE1A /* CameraOrientationTests.swift in Sources */, 977A25222D5A49EC00931E34 /* CameraFocusTests.swift in Sources */, + 978D90B42D5F630300CD817E /* StreamingTests.swift in Sources */, 7F29EB412D281C7E00740257 /* MockCaptureSession.m in Sources */, - 7FD582122D579650003B1200 /* MockWritableData.m in Sources */, 7FCEDD352D43C2B900EA1CA8 /* MockDeviceOrientationProvider.m in Sources */, + 977CAC9F2D5E5180001E5DC3 /* ThreadSafeEventChannelTests.swift in Sources */, 7FCEDD362D43C2B900EA1CA8 /* MockCaptureDevice.m in Sources */, 7F8FD22C2D4D07DD001AF2C1 /* MockFlutterTextureRegistry.m in Sources */, 97C0FFAE2D5E023200A36284 /* SavePhotoDelegateTests.swift in Sources */, diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraTestUtils.h b/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraTestUtils.h index a44e3aedcd05..aae88cfa7387 100644 --- a/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraTestUtils.h +++ b/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraTestUtils.h @@ -30,4 +30,11 @@ extern CMSampleBufferRef FLTCreateTestSampleBuffer(void); /// @return a test audio sample buffer. extern CMSampleBufferRef FLTCreateTestAudioSampleBuffer(void); +/// Calls `dispatch_queue_set_specific` with a key that is used to identify the queue. +/// This method is needed for comaptibility of Swift tests with Objective-C code. +/// In Swift, the API for settinng key-value pairs on a queue is different, so Swift tests +/// need to call this method to set the key-value pair on the queue in a way that's +/// compatible withn the existing Objective-C code. +extern void FLTdispatchQueueSetSpecific(dispatch_queue_t queue, const void *key); + NS_ASSUME_NONNULL_END diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraTestUtils.m b/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraTestUtils.m index 4b99e23d11fa..1e2f5f6bde06 100644 --- a/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraTestUtils.m +++ b/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraTestUtils.m @@ -127,3 +127,7 @@ CMSampleBufferRef FLTCreateTestAudioSampleBuffer(void) { CFRelease(formatDescription); return sampleBuffer; } + +void FLTdispatchQueueSetSpecific(dispatch_queue_t queue, const void *key) { + dispatch_queue_set_specific(queue, key, (void *)key, NULL); +} diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/FLTCamPhotoCaptureTests.m b/packages/camera/camera_avfoundation/example/ios/RunnerTests/FLTCamPhotoCaptureTests.m deleted file mode 100644 index 425481d263d6..000000000000 --- a/packages/camera/camera_avfoundation/example/ios/RunnerTests/FLTCamPhotoCaptureTests.m +++ /dev/null @@ -1,216 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -@import camera_avfoundation; -#if __has_include() -@import camera_avfoundation.Test; -#endif -@import AVFoundation; -@import XCTest; - -#import "CameraTestUtils.h" -#import "MockCaptureDevice.h" -#import "MockCapturePhotoOutput.h" - -/// Includes test cases related to photo capture operations for FLTCam class. -@interface FLTCamPhotoCaptureTests : XCTestCase - -@end - -@implementation FLTCamPhotoCaptureTests - -- (FLTCam *)createCamWithCaptureSessionQueue:(dispatch_queue_t)captureSessionQueue { - FLTCamConfiguration *configuration = FLTCreateTestCameraConfiguration(); - configuration.captureSessionQueue = captureSessionQueue; - return FLTCreateCamWithConfiguration(configuration); -} - -- (void)testCaptureToFile_mustReportErrorToResultIfSavePhotoDelegateCompletionsWithError { - XCTestExpectation *errorExpectation = - [self expectationWithDescription: - @"Must send error to result if save photo delegate completes with error."]; - - dispatch_queue_t captureSessionQueue = dispatch_queue_create("capture_session_queue", NULL); - dispatch_queue_set_specific(captureSessionQueue, FLTCaptureSessionQueueSpecific, - (void *)FLTCaptureSessionQueueSpecific, NULL); - FLTCam *cam = FLTCreateCamWithCaptureSessionQueue(captureSessionQueue); - - NSError *error = [NSError errorWithDomain:@"test" code:0 userInfo:nil]; - - MockCapturePhotoOutput *mockOutput = [[MockCapturePhotoOutput alloc] init]; - mockOutput.capturePhotoWithSettingsStub = - ^(AVCapturePhotoSettings *settings, NSObject *photoDelegate) { - FLTSavePhotoDelegate *delegate = cam.inProgressSavePhotoDelegates[@(settings.uniqueID)]; - // Completion runs on IO queue. - dispatch_queue_t ioQueue = dispatch_queue_create("io_queue", NULL); - dispatch_async(ioQueue, ^{ - delegate.completionHandler(nil, error); - }); - }; - cam.capturePhotoOutput = mockOutput; - - // `FLTCam::captureToFile` runs on capture session queue. - dispatch_async(captureSessionQueue, ^{ - [cam captureToFileWithCompletion:^(NSString *result, FlutterError *error) { - XCTAssertNil(result); - XCTAssertNotNil(error); - [errorExpectation fulfill]; - }]; - }); - - [self waitForExpectationsWithTimeout:30 handler:nil]; -} - -- (void)testCaptureToFile_mustReportPathToResultIfSavePhotoDelegateCompletionsWithPath { - XCTestExpectation *pathExpectation = - [self expectationWithDescription: - @"Must send file path to result if save photo delegate completes with file path."]; - - dispatch_queue_t captureSessionQueue = dispatch_queue_create("capture_session_queue", NULL); - dispatch_queue_set_specific(captureSessionQueue, FLTCaptureSessionQueueSpecific, - (void *)FLTCaptureSessionQueueSpecific, NULL); - FLTCam *cam = [self createCamWithCaptureSessionQueue:captureSessionQueue]; - - NSString *filePath = @"test"; - - MockCapturePhotoOutput *mockOutput = [[MockCapturePhotoOutput alloc] init]; - mockOutput.capturePhotoWithSettingsStub = - ^(AVCapturePhotoSettings *settings, NSObject *photoDelegate) { - FLTSavePhotoDelegate *delegate = cam.inProgressSavePhotoDelegates[@(settings.uniqueID)]; - // Completion runs on IO queue. - dispatch_queue_t ioQueue = dispatch_queue_create("io_queue", NULL); - dispatch_async(ioQueue, ^{ - delegate.completionHandler(filePath, nil); - }); - }; - cam.capturePhotoOutput = mockOutput; - - // `FLTCam::captureToFile` runs on capture session queue. - dispatch_async(captureSessionQueue, ^{ - [cam captureToFileWithCompletion:^(NSString *result, FlutterError *error) { - XCTAssertEqual(result, filePath); - [pathExpectation fulfill]; - }]; - }); - [self waitForExpectationsWithTimeout:30 handler:nil]; -} - -- (void)testCaptureToFile_mustReportFileExtensionWithHeifWhenHEVCIsAvailableAndFileFormatIsHEIF { - XCTestExpectation *expectation = - [self expectationWithDescription: - @"Test must set extension to heif if availablePhotoCodecTypes contains HEVC."]; - dispatch_queue_t captureSessionQueue = dispatch_queue_create("capture_session_queue", NULL); - dispatch_queue_set_specific(captureSessionQueue, FLTCaptureSessionQueueSpecific, - (void *)FLTCaptureSessionQueueSpecific, NULL); - FLTCam *cam = [self createCamWithCaptureSessionQueue:captureSessionQueue]; - [cam setImageFileFormat:FCPPlatformImageFileFormatHeif]; - - MockCapturePhotoOutput *mockOutput = [[MockCapturePhotoOutput alloc] init]; - mockOutput.availablePhotoCodecTypes = @[ AVVideoCodecTypeHEVC ]; - mockOutput.capturePhotoWithSettingsStub = - ^(AVCapturePhotoSettings *settings, NSObject *photoDelegate) { - FLTSavePhotoDelegate *delegate = cam.inProgressSavePhotoDelegates[@(settings.uniqueID)]; - // Completion runs on IO queue. - dispatch_queue_t ioQueue = dispatch_queue_create("io_queue", NULL); - dispatch_async(ioQueue, ^{ - delegate.completionHandler(delegate.filePath, nil); - }); - }; - cam.capturePhotoOutput = mockOutput; - - // `FLTCam::captureToFile` runs on capture session queue. - dispatch_async(captureSessionQueue, ^{ - [cam captureToFileWithCompletion:^(NSString *filePath, FlutterError *error) { - XCTAssertEqualObjects([filePath pathExtension], @"heif"); - [expectation fulfill]; - }]; - }); - [self waitForExpectationsWithTimeout:30 handler:nil]; -} - -- (void)testCaptureToFile_mustReportFileExtensionWithJpgWhenHEVCNotAvailableAndFileFormatIsHEIF { - XCTestExpectation *expectation = [self - expectationWithDescription: - @"Test must set extension to jpg if availablePhotoCodecTypes does not contain HEVC."]; - dispatch_queue_t captureSessionQueue = dispatch_queue_create("capture_session_queue", NULL); - dispatch_queue_set_specific(captureSessionQueue, FLTCaptureSessionQueueSpecific, - (void *)FLTCaptureSessionQueueSpecific, NULL); - FLTCam *cam = [self createCamWithCaptureSessionQueue:captureSessionQueue]; - [cam setImageFileFormat:FCPPlatformImageFileFormatHeif]; - - MockCapturePhotoOutput *mockOutput = [[MockCapturePhotoOutput alloc] init]; - mockOutput.capturePhotoWithSettingsStub = - ^(AVCapturePhotoSettings *settings, NSObject *photoDelegate) { - FLTSavePhotoDelegate *delegate = cam.inProgressSavePhotoDelegates[@(settings.uniqueID)]; - // Completion runs on IO queue. - dispatch_queue_t ioQueue = dispatch_queue_create("io_queue", NULL); - dispatch_async(ioQueue, ^{ - delegate.completionHandler(delegate.filePath, nil); - }); - }; - cam.capturePhotoOutput = mockOutput; - - // `FLTCam::captureToFile` runs on capture session queue. - dispatch_async(captureSessionQueue, ^{ - [cam captureToFileWithCompletion:^(NSString *filePath, FlutterError *error) { - XCTAssertEqualObjects([filePath pathExtension], @"jpg"); - [expectation fulfill]; - }]; - }); - [self waitForExpectationsWithTimeout:30 handler:nil]; -} - -- (void)testCaptureToFile_handlesTorchMode { - XCTestExpectation *pathExpectation = - [self expectationWithDescription: - @"Must send file path to result if save photo delegate completes with file path."]; - XCTestExpectation *setTorchExpectation = - [self expectationWithDescription:@"Should set torch mode to AVCaptureTorchModeOn."]; - - MockCaptureDevice *captureDeviceMock = [[MockCaptureDevice alloc] init]; - captureDeviceMock.hasTorch = YES; - captureDeviceMock.isTorchAvailable = YES; - captureDeviceMock.torchMode = AVCaptureTorchModeAuto; - captureDeviceMock.setTorchModeStub = ^(AVCaptureTorchMode mode) { - if (mode == AVCaptureTorchModeOn) { - [setTorchExpectation fulfill]; - } - }; - - dispatch_queue_t captureSessionQueue = dispatch_queue_create("capture_session_queue", NULL); - dispatch_queue_set_specific(captureSessionQueue, FLTCaptureSessionQueueSpecific, - (void *)FLTCaptureSessionQueueSpecific, NULL); - - FLTCamConfiguration *configuration = FLTCreateTestCameraConfiguration(); - configuration.captureSessionQueue = captureSessionQueue; - configuration.captureDeviceFactory = ^NSObject * { return captureDeviceMock; }; - FLTCam *cam = FLTCreateCamWithConfiguration(configuration); - - NSString *filePath = @"test"; - - MockCapturePhotoOutput *mockOutput = [[MockCapturePhotoOutput alloc] init]; - mockOutput.capturePhotoWithSettingsStub = - ^(AVCapturePhotoSettings *settings, NSObject *photoDelegate) { - FLTSavePhotoDelegate *delegate = cam.inProgressSavePhotoDelegates[@(settings.uniqueID)]; - // Completion runs on IO queue. - dispatch_queue_t ioQueue = dispatch_queue_create("io_queue", NULL); - dispatch_async(ioQueue, ^{ - delegate.completionHandler(filePath, nil); - }); - }; - cam.capturePhotoOutput = mockOutput; - - // `FLTCam::captureToFile` runs on capture session queue. - dispatch_async(captureSessionQueue, ^{ - [cam setFlashMode:FCPPlatformFlashModeTorch - withCompletion:^(FlutterError *_){ - }]; - [cam captureToFileWithCompletion:^(NSString *result, FlutterError *error) { - XCTAssertEqual(result, filePath); - [pathExpectation fulfill]; - }]; - }); - [self waitForExpectationsWithTimeout:30 handler:nil]; -} -@end diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/FLTCamSampleBufferTests.m b/packages/camera/camera_avfoundation/example/ios/RunnerTests/FLTCamSampleBufferTests.m deleted file mode 100644 index 83fbd57504c1..000000000000 --- a/packages/camera/camera_avfoundation/example/ios/RunnerTests/FLTCamSampleBufferTests.m +++ /dev/null @@ -1,394 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -@import camera_avfoundation; -#if __has_include() -@import camera_avfoundation.Test; -#endif -@import AVFoundation; -@import XCTest; -#import "CameraTestUtils.h" -#import "MockAssetWriter.h" -#import "MockCaptureConnection.h" - -@import camera_avfoundation; -@import AVFoundation; - -@interface FakeMediaSettingsAVWrapper : FLTCamMediaSettingsAVWrapper -@property(readonly, nonatomic) MockAssetWriterInput *inputMock; -@end - -@implementation FakeMediaSettingsAVWrapper -- (instancetype)initWithInputMock:(MockAssetWriterInput *)inputMock { - _inputMock = inputMock; - return self; -} - -- (BOOL)lockDevice:(AVCaptureDevice *)captureDevice error:(NSError **)outError { - return YES; -} - -- (void)unlockDevice:(AVCaptureDevice *)captureDevice { -} - -- (void)beginConfigurationForSession:(id)videoCaptureSession { -} - -- (void)commitConfigurationForSession:(id)videoCaptureSession { -} - -- (void)setMinFrameDuration:(CMTime)duration onDevice:(AVCaptureDevice *)captureDevice { -} - -- (void)setMaxFrameDuration:(CMTime)duration onDevice:(AVCaptureDevice *)captureDevice { -} - -- (id)assetWriterAudioInputWithOutputSettings: - (nullable NSDictionary *)outputSettings { - return _inputMock; -} - -- (id)assetWriterVideoInputWithOutputSettings: - (nullable NSDictionary *)outputSettings { - return _inputMock; -} - -- (void)addInput:(AVAssetWriterInput *)writerInput toAssetWriter:(AVAssetWriter *)writer { -} - -- (NSDictionary *) - recommendedVideoSettingsForAssetWriterWithFileType:(AVFileType)fileType - forOutput:(AVCaptureVideoDataOutput *)output { - return @{}; -} -@end - -/// Includes test cases related to sample buffer handling for FLTCam class. -@interface FLTCamSampleBufferTests : XCTestCase -@end - -@implementation FLTCamSampleBufferTests - -- (FLTCam *)createCameraWithAssetWriter:(MockAssetWriter *)assetWriter - adaptor:(MockAssetWriterInputPixelBufferAdaptor *)adaptor - input:(MockAssetWriterInput *)input { - FLTCamConfiguration *configuration = FLTCreateTestCameraConfiguration(); - configuration.mediaSettings = - [FCPPlatformMediaSettings makeWithResolutionPreset:FCPPlatformResolutionPresetMedium - framesPerSecond:nil - videoBitrate:nil - audioBitrate:nil - enableAudio:YES]; - configuration.mediaSettingsWrapper = [[FakeMediaSettingsAVWrapper alloc] initWithInputMock:input]; - - configuration.assetWriterFactory = - ^NSObject *_Nonnull(NSURL *url, AVFileType fileType, NSError **error) { - return assetWriter; - }; - configuration.inputPixelBufferAdaptorFactory = - ^id _Nonnull(NSObject *input, - NSDictionary *settings) { - return adaptor; - }; - - return FLTCreateCamWithConfiguration(configuration); -} - -- (void)testSampleBufferCallbackQueueMustBeCaptureSessionQueue { - dispatch_queue_t captureSessionQueue = dispatch_queue_create("testing", NULL); - FLTCam *camera = FLTCreateCamWithCaptureSessionQueue(captureSessionQueue); - XCTAssertEqual(captureSessionQueue, camera.captureVideoOutput.sampleBufferCallbackQueue, - @"Sample buffer callback queue must be the capture session queue."); -} - -- (void)testCopyPixelBuffer { - FLTCam *camera = FLTCreateCamWithConfiguration(FLTCreateTestCameraConfiguration()); - MockCaptureConnection *connectionMock = [[MockCaptureConnection alloc] init]; - CMSampleBufferRef capturedSampleBuffer = FLTCreateTestSampleBuffer(); - CVPixelBufferRef capturedPixelBuffer = CMSampleBufferGetImageBuffer(capturedSampleBuffer); - // Mimic sample buffer callback when captured a new video sample - [camera captureOutput:camera.captureVideoOutput - didOutputSampleBuffer:capturedSampleBuffer - fromConnection:connectionMock]; - CVPixelBufferRef deliveriedPixelBuffer = [camera copyPixelBuffer]; - XCTAssertEqual(deliveriedPixelBuffer, capturedPixelBuffer, - @"FLTCam must deliver the latest captured pixel buffer to copyPixelBuffer API."); - CFRelease(capturedSampleBuffer); - CFRelease(deliveriedPixelBuffer); -} - -- (void)testDidOutputSampleBuffer_mustNotChangeSampleBufferRetainCountAfterPauseResumeRecording { - FLTCam *camera = FLTCreateCamWithConfiguration(FLTCreateTestCameraConfiguration()); - MockCaptureConnection *connectionMock = [[MockCaptureConnection alloc] init]; - CMSampleBufferRef sampleBuffer = FLTCreateTestSampleBuffer(); - - // Pause then resume the recording. - [camera - startVideoRecordingWithCompletion:^(FlutterError *_Nullable error) { - } - messengerForStreaming:nil]; - [camera pauseVideoRecording]; - [camera resumeVideoRecording]; - - [camera captureOutput:camera.captureVideoOutput - didOutputSampleBuffer:sampleBuffer - fromConnection:connectionMock]; - XCTAssertEqual(CFGetRetainCount(sampleBuffer), 1, - @"didOutputSampleBuffer must not change the sample buffer retain count after " - @"pause resume recording."); - CFRelease(sampleBuffer); -} - -- (void)testDidOutputSampleBufferIgnoreAudioSamplesBeforeVideoSamples { - MockAssetWriter *writerMock = [[MockAssetWriter alloc] init]; - MockAssetWriterInputPixelBufferAdaptor *adaptorMock = - [[MockAssetWriterInputPixelBufferAdaptor alloc] init]; - MockAssetWriterInput *inputMock = [[MockAssetWriterInput alloc] init]; - MockCaptureConnection *connectionMock = [[MockCaptureConnection alloc] init]; - - __block AVAssetWriterStatus status = AVAssetWriterStatusUnknown; - writerMock.startWritingStub = ^{ - status = AVAssetWriterStatusWriting; - }; - writerMock.statusStub = ^AVAssetWriterStatus { - return status; - }; - - FLTCam *camera = [self createCameraWithAssetWriter:writerMock - adaptor:adaptorMock - input:inputMock]; - CMSampleBufferRef videoSample = FLTCreateTestSampleBuffer(); - CMSampleBufferRef audioSample = FLTCreateTestAudioSampleBuffer(); - - __block NSArray *writtenSamples = @[]; - - adaptorMock.appendPixelBufferStub = ^BOOL(CVPixelBufferRef buffer, CMTime time) { - writtenSamples = [writtenSamples arrayByAddingObject:@"video"]; - return YES; - }; - - inputMock.readyForMoreMediaData = YES; - inputMock.appendSampleBufferStub = ^BOOL(CMSampleBufferRef buffer) { - writtenSamples = [writtenSamples arrayByAddingObject:@"audio"]; - return YES; - }; - - [camera - startVideoRecordingWithCompletion:^(FlutterError *_Nullable error) { - } - messengerForStreaming:nil]; - - [camera captureOutput:nil didOutputSampleBuffer:audioSample fromConnection:connectionMock]; - [camera captureOutput:nil didOutputSampleBuffer:audioSample fromConnection:connectionMock]; - [camera captureOutput:camera.captureVideoOutput - didOutputSampleBuffer:videoSample - fromConnection:connectionMock]; - [camera captureOutput:nil didOutputSampleBuffer:audioSample fromConnection:connectionMock]; - - NSArray *expectedSamples = @[ @"video", @"audio" ]; - XCTAssertEqualObjects(writtenSamples, expectedSamples, @"First appended sample must be video."); - - CFRelease(videoSample); - CFRelease(audioSample); -} - -- (void)testDidOutputSampleBufferSampleTimesMustBeNumericAfterPauseResume { - MockAssetWriter *writerMock = [[MockAssetWriter alloc] init]; - MockAssetWriterInputPixelBufferAdaptor *adaptorMock = - [[MockAssetWriterInputPixelBufferAdaptor alloc] init]; - MockAssetWriterInput *inputMock = [[MockAssetWriterInput alloc] init]; - MockCaptureConnection *connectionMock = [[MockCaptureConnection alloc] init]; - - FLTCam *camera = [self createCameraWithAssetWriter:writerMock - adaptor:adaptorMock - input:inputMock]; - CMSampleBufferRef videoSample = FLTCreateTestSampleBuffer(); - CMSampleBufferRef audioSample = FLTCreateTestAudioSampleBuffer(); - - __block AVAssetWriterStatus status = AVAssetWriterStatusUnknown; - writerMock.startWritingStub = ^{ - status = AVAssetWriterStatusWriting; - }; - writerMock.statusStub = ^AVAssetWriterStatus { - return status; - }; - - __block BOOL videoAppended = NO; - adaptorMock.appendPixelBufferStub = ^BOOL(CVPixelBufferRef buffer, CMTime time) { - XCTAssert(CMTIME_IS_NUMERIC(time)); - videoAppended = YES; - return YES; - }; - - __block BOOL audioAppended = NO; - inputMock.readyForMoreMediaData = YES; - inputMock.appendSampleBufferStub = ^BOOL(CMSampleBufferRef buffer) { - CMTime sampleTime = CMSampleBufferGetPresentationTimeStamp(buffer); - XCTAssert(CMTIME_IS_NUMERIC(sampleTime)); - audioAppended = YES; - return YES; - }; - - [camera - startVideoRecordingWithCompletion:^(FlutterError *_Nullable error) { - } - messengerForStreaming:nil]; - - [camera pauseVideoRecording]; - [camera resumeVideoRecording]; - - [camera captureOutput:camera.captureVideoOutput - didOutputSampleBuffer:videoSample - fromConnection:connectionMock]; - [camera captureOutput:nil didOutputSampleBuffer:audioSample fromConnection:connectionMock]; - [camera captureOutput:camera.captureVideoOutput - didOutputSampleBuffer:videoSample - fromConnection:connectionMock]; - [camera captureOutput:nil didOutputSampleBuffer:audioSample fromConnection:connectionMock]; - XCTAssert(videoAppended && audioAppended, @"Video or audio was not appended."); - - CFRelease(videoSample); - CFRelease(audioSample); -} - -- (void)testDidOutputSampleBufferMustNotAppendSampleWhenReadyForMoreMediaDataIsNo { - MockAssetWriter *writerMock = [[MockAssetWriter alloc] init]; - MockAssetWriterInputPixelBufferAdaptor *adaptorMock = - [[MockAssetWriterInputPixelBufferAdaptor alloc] init]; - MockAssetWriterInput *inputMock = [[MockAssetWriterInput alloc] init]; - MockCaptureConnection *connectionMock = [[MockCaptureConnection alloc] init]; - FLTCam *camera = [self createCameraWithAssetWriter:writerMock - adaptor:adaptorMock - input:inputMock]; - - CMSampleBufferRef videoSample = FLTCreateTestSampleBuffer(); - - __block BOOL sampleAppended = NO; - adaptorMock.appendPixelBufferStub = ^BOOL(CVPixelBufferRef buffer, CMTime time) { - sampleAppended = YES; - return YES; - }; - - [camera - startVideoRecordingWithCompletion:^(FlutterError *_Nullable error) { - } - messengerForStreaming:nil]; - - inputMock.readyForMoreMediaData = YES; - sampleAppended = NO; - [camera captureOutput:camera.captureVideoOutput - didOutputSampleBuffer:videoSample - fromConnection:connectionMock]; - XCTAssertTrue(sampleAppended, @"Sample was not appended."); - - inputMock.readyForMoreMediaData = NO; - sampleAppended = NO; - [camera captureOutput:camera.captureVideoOutput - didOutputSampleBuffer:videoSample - fromConnection:connectionMock]; - XCTAssertFalse(sampleAppended, @"Sample cannot be appended when readyForMoreMediaData is NO."); - - CFRelease(videoSample); -} - -- (void)testStopVideoRecordingWithCompletionMustCallCompletion { - MockAssetWriter *writerMock = [[MockAssetWriter alloc] init]; - MockAssetWriterInputPixelBufferAdaptor *adaptorMock = - [[MockAssetWriterInputPixelBufferAdaptor alloc] init]; - MockAssetWriterInput *inputMock = [[MockAssetWriterInput alloc] init]; - FLTCam *camera = [self createCameraWithAssetWriter:writerMock - adaptor:adaptorMock - input:inputMock]; - - __block AVAssetWriterStatus status = AVAssetWriterStatusUnknown; - writerMock.startWritingStub = ^{ - status = AVAssetWriterStatusWriting; - }; - writerMock.statusStub = ^AVAssetWriterStatus { - return status; - }; - writerMock.finishWritingStub = ^(void (^param)(void)) { - XCTAssert(writerMock.status == AVAssetWriterStatusWriting, - @"Cannot call finishWritingWithCompletionHandler when status is " - @"not AVAssetWriterStatusWriting."); - void (^handler)(void) = param; - handler(); - }; - - [camera - startVideoRecordingWithCompletion:^(FlutterError *_Nullable error) { - } - messengerForStreaming:nil]; - - __block BOOL completionCalled = NO; - [camera - stopVideoRecordingWithCompletion:^(NSString *_Nullable path, FlutterError *_Nullable error) { - completionCalled = YES; - }]; - XCTAssert(completionCalled, @"Completion was not called."); -} - -- (void)testStartWritingShouldNotBeCalledBetweenSampleCreationAndAppending { - MockAssetWriter *writerMock = [[MockAssetWriter alloc] init]; - MockAssetWriterInputPixelBufferAdaptor *adaptorMock = - [[MockAssetWriterInputPixelBufferAdaptor alloc] init]; - MockAssetWriterInput *inputMock = [[MockAssetWriterInput alloc] init]; - MockCaptureConnection *connectionMock = [[MockCaptureConnection alloc] init]; - FLTCam *camera = [self createCameraWithAssetWriter:writerMock - adaptor:adaptorMock - input:inputMock]; - - CMSampleBufferRef videoSample = FLTCreateTestSampleBuffer(); - - __block BOOL startWritingCalled = NO; - writerMock.startWritingStub = ^{ - startWritingCalled = YES; - }; - - __block BOOL videoAppended = NO; - adaptorMock.appendPixelBufferStub = ^BOOL(CVPixelBufferRef buffer, CMTime time) { - videoAppended = YES; - return YES; - }; - - inputMock.readyForMoreMediaData = YES; - - [camera - startVideoRecordingWithCompletion:^(FlutterError *_Nullable error) { - } - messengerForStreaming:nil]; - - BOOL startWritingCalledBefore = startWritingCalled; - [camera captureOutput:camera.captureVideoOutput - didOutputSampleBuffer:videoSample - fromConnection:connectionMock]; - XCTAssert((startWritingCalledBefore && videoAppended) || (startWritingCalled && !videoAppended), - @"The startWriting was called between sample creation and appending."); - - [camera captureOutput:camera.captureVideoOutput - didOutputSampleBuffer:videoSample - fromConnection:connectionMock]; - XCTAssert(videoAppended, @"Video was not appended."); - - CFRelease(videoSample); -} - -- (void)testStartVideoRecordingWithCompletionShouldNotDisableMixWithOthers { - FLTCam *cam = FLTCreateCamWithCaptureSessionQueue(dispatch_queue_create("testing", NULL)); - - [AVAudioSession.sharedInstance setCategory:AVAudioSessionCategoryPlayback - withOptions:AVAudioSessionCategoryOptionMixWithOthers - error:nil]; - [cam - startVideoRecordingWithCompletion:^(FlutterError *_Nullable error) { - } - messengerForStreaming:nil]; - XCTAssert( - AVAudioSession.sharedInstance.categoryOptions & AVAudioSessionCategoryOptionMixWithOthers, - @"Flag MixWithOthers was removed."); - XCTAssert(AVAudioSession.sharedInstance.category == AVAudioSessionCategoryPlayAndRecord, - @"Category should be PlayAndRecord."); -} - -@end diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/Mocks/MockCapturePhotoOutput.h b/packages/camera/camera_avfoundation/example/ios/RunnerTests/Mocks/MockCapturePhotoOutput.h index 7a43852e3b5d..403c9582f32b 100644 --- a/packages/camera/camera_avfoundation/example/ios/RunnerTests/Mocks/MockCapturePhotoOutput.h +++ b/packages/camera/camera_avfoundation/example/ios/RunnerTests/Mocks/MockCapturePhotoOutput.h @@ -5,6 +5,8 @@ @import camera_avfoundation; @import AVFoundation; +NS_ASSUME_NONNULL_BEGIN + /// Mock implementation of `FLTCapturePhotoOutput` protocol which allows injecting a custom /// implementation. @interface MockCapturePhotoOutput : NSObject @@ -20,3 +22,5 @@ (AVCapturePhotoSettings *, NSObject *); @end + +NS_ASSUME_NONNULL_END diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/Mocks/MockEventChannel.h b/packages/camera/camera_avfoundation/example/ios/RunnerTests/Mocks/MockEventChannel.h deleted file mode 100644 index d92adf35c058..000000000000 --- a/packages/camera/camera_avfoundation/example/ios/RunnerTests/Mocks/MockEventChannel.h +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -@import camera_avfoundation; -#if __has_include() -@import camera_avfoundation.Test; -#endif -@import Flutter; - -NS_ASSUME_NONNULL_BEGIN - -/// A mock implementation of `FLTEventChannel` that allows injecting a custom stream handler. -@interface MockEventChannel : NSObject - -/// Overrides the default implementation of setting the stream handler. -@property(nonatomic, copy) void (^setStreamHandlerStub)(NSObject *); - -@end - -NS_ASSUME_NONNULL_END diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/Mocks/MockEventChannel.m b/packages/camera/camera_avfoundation/example/ios/RunnerTests/Mocks/MockEventChannel.m deleted file mode 100644 index 7190cfb17714..000000000000 --- a/packages/camera/camera_avfoundation/example/ios/RunnerTests/Mocks/MockEventChannel.m +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#import "MockEventChannel.h" - -@implementation MockEventChannel - -- (void)setStreamHandler:(NSObject *)handler { - if (self.setStreamHandlerStub) { - self.setStreamHandlerStub(handler); - } -} - -@end diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/Mocks/MockEventChannel.swift b/packages/camera/camera_avfoundation/example/ios/RunnerTests/Mocks/MockEventChannel.swift new file mode 100644 index 000000000000..84c6a40d30ef --- /dev/null +++ b/packages/camera/camera_avfoundation/example/ios/RunnerTests/Mocks/MockEventChannel.swift @@ -0,0 +1,13 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/// A mock implementation of `FLTEventChannel` that allows injecting a custom implementation +/// for setting a stream handler. +final class MockEventChannel: NSObject, FLTEventChannel { + var setStreamHandlerStub: ((FlutterStreamHandler?) -> Void)? + + func setStreamHandler(_ handler: (FlutterStreamHandler & NSObjectProtocol)?) { + setStreamHandlerStub?(handler) + } +} diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/Mocks/MockWritableData.h b/packages/camera/camera_avfoundation/example/ios/RunnerTests/Mocks/MockWritableData.h deleted file mode 100644 index 746765050edc..000000000000 --- a/packages/camera/camera_avfoundation/example/ios/RunnerTests/Mocks/MockWritableData.h +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -@import camera_avfoundation; -@import AVFoundation; - -/// A mock implementation of `FLTWritableData` that allows injecting a custom implementation -/// for writing to a file. -@interface MockWritableData : NSObject - -/// A stub that replaces the default implementation of `writeToFile:options:error:`. -@property(nonatomic, copy) BOOL (^writeToFileStub) - (NSString *path, NSDataWritingOptions options, NSError **error); - -@end diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/Mocks/MockWritableData.m b/packages/camera/camera_avfoundation/example/ios/RunnerTests/Mocks/MockWritableData.m deleted file mode 100644 index 8b0a354a8cb6..000000000000 --- a/packages/camera/camera_avfoundation/example/ios/RunnerTests/Mocks/MockWritableData.m +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#import "MockWritableData.h" - -@implementation MockWritableData - -- (BOOL)writeToFile:(NSString *)path - options:(NSDataWritingOptions)writeOptionsMask - error:(NSError **)errorPtr { - if (self.writeToFileStub) { - return _writeToFileStub(path, writeOptionsMask, errorPtr); - } - return YES; -} - -@end diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/Mocks/MockWritableData.swift b/packages/camera/camera_avfoundation/example/ios/RunnerTests/Mocks/MockWritableData.swift new file mode 100644 index 000000000000..912c77cb48ee --- /dev/null +++ b/packages/camera/camera_avfoundation/example/ios/RunnerTests/Mocks/MockWritableData.swift @@ -0,0 +1,15 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/// A mock implementation of `FLTWritableData` that allows injecting a custom implementation +/// for writing to a file. +final class MockWritableData: NSObject, FLTWritableData { + var writeToFileStub: ((String, NSData.WritingOptions) throws -> Void)? + + func write(toFile path: String, options writeOptionsMask: NSData.WritingOptions) throws { + if let stub = self.writeToFileStub { + try stub(path, writeOptionsMask) + } + } +} diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/PhotoCaptureTests.swift b/packages/camera/camera_avfoundation/example/ios/RunnerTests/PhotoCaptureTests.swift new file mode 100644 index 000000000000..fe26009bc2e3 --- /dev/null +++ b/packages/camera/camera_avfoundation/example/ios/RunnerTests/PhotoCaptureTests.swift @@ -0,0 +1,199 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import AVFoundation +import XCTest + +@testable import camera_avfoundation + +/// Includes test cases related to photo capture operations for FLTCam class. +final class PhotoCaptureTests: XCTestCase { + private func createCam(with captureSessionQueue: DispatchQueue) -> FLTCam { + let configuration = FLTCreateTestCameraConfiguration() + configuration.captureSessionQueue = captureSessionQueue + return FLTCreateCamWithConfiguration(configuration) + } + + func testCaptureToFile_mustReportErrorToResultIfSavePhotoDelegateCompletionsWithError() { + let errorExpectation = expectation( + description: "Must send error to result if save photo delegate completes with error.") + let captureSessionQueue = DispatchQueue(label: "capture_session_queue") + FLTdispatchQueueSetSpecific(captureSessionQueue, FLTCaptureSessionQueueSpecific) + let cam = createCam(with: captureSessionQueue) + let error = NSError(domain: "test", code: 0, userInfo: nil) + + let mockOutput = MockCapturePhotoOutput() + mockOutput.capturePhotoWithSettingsStub = { settings, photoDelegate in + let delegate = + cam.inProgressSavePhotoDelegates.object(forKey: settings.uniqueID) + as? FLTSavePhotoDelegate + // Completion runs on IO queue. + let ioQueue = DispatchQueue(label: "io_queue") + ioQueue.async { + delegate?.completionHandler(nil, error) + } + } + cam.capturePhotoOutput = mockOutput + + // `FLTCam::captureToFile` runs on capture session queue. + captureSessionQueue.async { + cam.captureToFile { result, error in + XCTAssertNil(result) + XCTAssertNotNil(error) + errorExpectation.fulfill() + } + } + + waitForExpectations(timeout: 30, handler: nil) + } + + func testCaptureToFile_mustReportPathToResultIfSavePhotoDelegateCompletionsWithPath() { + let pathExpectation = expectation( + description: "Must send file path to result if save photo delegate completes with file path.") + let captureSessionQueue = DispatchQueue(label: "capture_session_queue") + FLTdispatchQueueSetSpecific(captureSessionQueue, FLTCaptureSessionQueueSpecific) + let cam = createCam(with: captureSessionQueue) + let filePath = "test" + + let mockOutput = MockCapturePhotoOutput() + mockOutput.capturePhotoWithSettingsStub = { settings, photoDelegate in + let delegate = + cam.inProgressSavePhotoDelegates.object(forKey: settings.uniqueID) + as? FLTSavePhotoDelegate + // Completion runs on IO queue. + let ioQueue = DispatchQueue(label: "io_queue") + ioQueue.async { + delegate?.completionHandler(filePath, nil) + } + } + cam.capturePhotoOutput = mockOutput + + // `FLTCam::captureToFile` runs on capture session queue. + captureSessionQueue.async { + cam.captureToFile { result, error in + XCTAssertEqual(result, filePath) + pathExpectation.fulfill() + } + } + + waitForExpectations(timeout: 30, handler: nil) + } + + func testCaptureToFile_mustReportFileExtensionWithHeifWhenHEVCIsAvailableAndFileFormatIsHEIF() { + let expectation = self.expectation( + description: "Test must set extension to heif if availablePhotoCodecTypes contains HEVC.") + + let captureSessionQueue = DispatchQueue(label: "capture_session_queue") + FLTdispatchQueueSetSpecific(captureSessionQueue, FLTCaptureSessionQueueSpecific) + let cam = createCam(with: captureSessionQueue) + cam.setImageFileFormat(FCPPlatformImageFileFormat.heif) + + let mockOutput = MockCapturePhotoOutput() + mockOutput.availablePhotoCodecTypes = [AVVideoCodecType.hevc] + mockOutput.capturePhotoWithSettingsStub = { settings, photoDelegate in + let delegate = + cam.inProgressSavePhotoDelegates.object(forKey: settings.uniqueID) + as? FLTSavePhotoDelegate + // Completion runs on IO queue. + let ioQueue = DispatchQueue(label: "io_queue") + ioQueue.async { + delegate?.completionHandler(delegate?.filePath, nil) + } + } + cam.capturePhotoOutput = mockOutput + + // `FLTCam::captureToFile` runs on capture session queue. + captureSessionQueue.async { + cam.captureToFile { filePath, error in + XCTAssertEqual((filePath! as NSString).pathExtension, "heif") + expectation.fulfill() + } + } + + waitForExpectations(timeout: 30, handler: nil) + } + + func testCaptureToFile_mustReportFileExtensionWithJpgWhenHEVCNotAvailableAndFileFormatIsHEIF() { + let expectation = self.expectation( + description: + "Test must set extension to jpg if availablePhotoCodecTypes does not contain HEVC.") + + let captureSessionQueue = DispatchQueue(label: "capture_session_queue") + FLTdispatchQueueSetSpecific(captureSessionQueue, FLTCaptureSessionQueueSpecific) + let cam = createCam(with: captureSessionQueue) + cam.setImageFileFormat(FCPPlatformImageFileFormat.heif) + + let mockOutput = MockCapturePhotoOutput() + mockOutput.capturePhotoWithSettingsStub = { settings, photoDelegate in + let delegate = + cam.inProgressSavePhotoDelegates.object(forKey: settings.uniqueID) + as? FLTSavePhotoDelegate + // Completion runs on IO queue. + let ioQueue = DispatchQueue(label: "io_queue") + ioQueue.async { + delegate?.completionHandler(delegate?.filePath, nil) + } + } + cam.capturePhotoOutput = mockOutput + + // `FLTCam::captureToFile` runs on capture session queue. + captureSessionQueue.async { + cam.captureToFile { filePath, error in + XCTAssertEqual((filePath! as NSString).pathExtension, "jpg") + expectation.fulfill() + } + } + + waitForExpectations(timeout: 30, handler: nil) + } + + func testCaptureToFile_handlesTorchMode() { + let pathExpectation = expectation( + description: "Must send file path to result if save photo delegate completes with file path.") + let setTorchExpectation = expectation( + description: "Should set torch mode to AVCaptureTorchModeOn.") + + let captureDeviceMock = MockCaptureDevice() + captureDeviceMock.hasTorch = true + captureDeviceMock.isTorchAvailable = true + captureDeviceMock.torchMode = .auto + captureDeviceMock.setTorchModeStub = { mode in + if mode == .on { + setTorchExpectation.fulfill() + } + } + + let captureSessionQueue = DispatchQueue(label: "capture_session_queue") + FLTdispatchQueueSetSpecific(captureSessionQueue, FLTCaptureSessionQueueSpecific) + let configuration = FLTCreateTestCameraConfiguration() + configuration.captureSessionQueue = captureSessionQueue + configuration.captureDeviceFactory = { captureDeviceMock } + let cam = FLTCreateCamWithConfiguration(configuration) + + let filePath = "test" + let mockOutput = MockCapturePhotoOutput() + mockOutput.capturePhotoWithSettingsStub = { settings, photoDelegate in + let delegate = + cam.inProgressSavePhotoDelegates.object(forKey: settings.uniqueID) + as? FLTSavePhotoDelegate + // Completion runs on IO queue. + let ioQueue = DispatchQueue(label: "io_queue") + ioQueue.async { + delegate?.completionHandler(filePath, nil) + } + } + cam.capturePhotoOutput = mockOutput + + // `FLTCam::captureToFile` runs on capture session queue. + captureSessionQueue.async { + cam.setFlashMode(.torch) { _ in } + cam.captureToFile { result, error in + XCTAssertEqual(result, filePath) + pathExpectation.fulfill() + } + } + + waitForExpectations(timeout: 30, handler: nil) + } +} diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/RunnerTests-Bridging-Header.h b/packages/camera/camera_avfoundation/example/ios/RunnerTests/RunnerTests-Bridging-Header.h index 4e0309c49098..bf3b2bd9e3ca 100644 --- a/packages/camera/camera_avfoundation/example/ios/RunnerTests/RunnerTests-Bridging-Header.h +++ b/packages/camera/camera_avfoundation/example/ios/RunnerTests/RunnerTests-Bridging-Header.h @@ -7,20 +7,21 @@ #import "camera_avfoundation/CameraPlugin_Test.h" #import "camera_avfoundation/FLTCamConfiguration.h" #import "camera_avfoundation/FLTCam_Test.h" +#import "camera_avfoundation/FLTSavePhotoDelegate.h" #import "camera_avfoundation/FLTThreadSafeEventChannel.h" // Mocks, protocols. #import "MockAssetWriter.h" #import "MockCameraDeviceDiscoverer.h" +#import "MockCaptureConnection.h" #import "MockCaptureDevice.h" #import "MockCaptureDeviceFormat.h" +#import "MockCapturePhotoOutput.h" #import "MockCaptureSession.h" #import "MockDeviceOrientationProvider.h" -#import "MockEventChannel.h" #import "MockFlutterBinaryMessenger.h" #import "MockFlutterTextureRegistry.h" #import "MockGlobalEventApi.h" -#import "MockWritableData.h" // Utils. #import "CameraTestUtils.h" diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/SampleBufferTests.swift b/packages/camera/camera_avfoundation/example/ios/RunnerTests/SampleBufferTests.swift new file mode 100644 index 000000000000..686f437a93c9 --- /dev/null +++ b/packages/camera/camera_avfoundation/example/ios/RunnerTests/SampleBufferTests.swift @@ -0,0 +1,316 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import AVFoundation +import XCTest + +@testable import camera_avfoundation + +private class FakeMediaSettingsAVWrapper: FLTCamMediaSettingsAVWrapper { + let inputMock: MockAssetWriterInput + + init(inputMock: MockAssetWriterInput) { + self.inputMock = inputMock + } + + override func lockDevice(_ captureDevice: FLTCaptureDevice) throws { + // No-op. + } + + override func unlockDevice(_ captureDevice: FLTCaptureDevice) { + // No-op. + } + + override func beginConfiguration(for videoCaptureSession: FLTCaptureSession) { + // No-op. + } + + override func commitConfiguration(for videoCaptureSession: FLTCaptureSession) { + // No-op. + } + + override func setMinFrameDuration(_ duration: CMTime, on captureDevice: FLTCaptureDevice) { + // No-op. + } + + override func setMaxFrameDuration(_ duration: CMTime, on captureDevice: FLTCaptureDevice) { + // No-op. + } + + override func assetWriterAudioInput(withOutputSettings outputSettings: [String: Any]?) + -> FLTAssetWriterInput + { + return inputMock + } + + override func assetWriterVideoInput(withOutputSettings outputSettings: [String: Any]?) + -> FLTAssetWriterInput + { + return inputMock + } + + override func addInput(_ writerInput: FLTAssetWriterInput, to writer: FLTAssetWriter) { + // No-op. + } + + override func recommendedVideoSettingsForAssetWriter( + withFileType fileType: AVFileType, for output: AVCaptureVideoDataOutput + ) -> [String: Any]? { + return [:] + } +} + +/// Includes test cases related to sample buffer handling for FLTCam class. +final class CameraSampleBufferTests: XCTestCase { + private func createCamera() -> ( + FLTCam, + MockAssetWriter, + MockAssetWriterInputPixelBufferAdaptor, + MockAssetWriterInput, + MockCaptureConnection + ) { + let assetWriter = MockAssetWriter() + let adaptor = MockAssetWriterInputPixelBufferAdaptor() + let input = MockAssetWriterInput() + + let configuration = FLTCreateTestCameraConfiguration() + configuration.mediaSettings = FCPPlatformMediaSettings.make( + with: .medium, + framesPerSecond: nil, + videoBitrate: nil, + audioBitrate: nil, + enableAudio: true) + configuration.mediaSettingsWrapper = FakeMediaSettingsAVWrapper(inputMock: input) + + configuration.assetWriterFactory = { url, fileType, error in + return assetWriter + } + configuration.inputPixelBufferAdaptorFactory = { input, settings in + return adaptor + } + + return ( + FLTCreateCamWithConfiguration(configuration), assetWriter, adaptor, input, + MockCaptureConnection() + ) + } + + func testSampleBufferCallbackQueueMustBeCaptureSessionQueue() { + let captureSessionQueue = DispatchQueue(label: "testing") + let camera = FLTCreateCamWithCaptureSessionQueue(captureSessionQueue) + XCTAssertEqual( + captureSessionQueue, camera.captureVideoOutput.sampleBufferCallbackQueue, + "Sample buffer callback queue must be the capture session queue.") + } + + func testCopyPixelBuffer() { + let (camera, _, _, _, connectionMock) = createCamera() + let capturedSampleBuffer = FLTCreateTestSampleBuffer().takeRetainedValue() + let capturedPixelBuffer = CMSampleBufferGetImageBuffer(capturedSampleBuffer)! + // Mimic sample buffer callback when captured a new video sample. + camera.captureOutput( + camera.captureVideoOutput, didOutputSampleBuffer: capturedSampleBuffer, from: connectionMock) + let deliveredPixelBuffer = camera.copyPixelBuffer()?.takeRetainedValue() + XCTAssertEqual( + deliveredPixelBuffer, capturedPixelBuffer, + "FLTCam must deliver the latest captured pixel buffer to copyPixelBuffer API.") + } + + func testDidOutputSampleBuffer_mustNotChangeSampleBufferRetainCountAfterPauseResumeRecording() { + let (camera, _, _, _, connectionMock) = createCamera() + let sampleBuffer = FLTCreateTestSampleBuffer().takeRetainedValue() + + let initialRetainCount = CFGetRetainCount(sampleBuffer) + + // Pause then resume the recording. + camera.startVideoRecording(completion: { error in }, messengerForStreaming: nil) + camera.pauseVideoRecording() + camera.resumeVideoRecording() + + camera.captureOutput( + camera.captureVideoOutput, didOutputSampleBuffer: sampleBuffer, from: connectionMock) + + let finalRetainCount = CFGetRetainCount(sampleBuffer) + XCTAssertEqual( + finalRetainCount, initialRetainCount, + "didOutputSampleBuffer must not change the sample buffer retain count after pause resume recording." + ) + } + + func testDidOutputSampleBufferIgnoreAudioSamplesBeforeVideoSamples() { + let (camera, writerMock, adaptorMock, inputMock, connectionMock) = createCamera() + var status = AVAssetWriter.Status.unknown + writerMock.startWritingStub = { + status = .writing + } + writerMock.statusStub = { + return status + } + + let videoSample = FLTCreateTestSampleBuffer().takeRetainedValue() + let audioSample = FLTCreateTestAudioSampleBuffer().takeRetainedValue() + + var writtenSamples: [String] = [] + adaptorMock.appendPixelBufferStub = { buffer, time in + writtenSamples.append("video") + return true + } + inputMock.readyForMoreMediaData = true + inputMock.appendSampleBufferStub = { buffer in + writtenSamples.append("audio") + return true + } + + camera.startVideoRecording(completion: { error in }, messengerForStreaming: nil) + camera.captureOutput(nil, didOutputSampleBuffer: audioSample, from: connectionMock) + camera.captureOutput(nil, didOutputSampleBuffer: audioSample, from: connectionMock) + camera.captureOutput( + camera.captureVideoOutput, didOutputSampleBuffer: videoSample, from: connectionMock) + camera.captureOutput(nil, didOutputSampleBuffer: audioSample, from: connectionMock) + + let expectedSamples = ["video", "audio"] + XCTAssertEqual(writtenSamples, expectedSamples, "First appended sample must be video.") + } + + func testDidOutputSampleBufferSampleTimesMustBeNumericAfterPauseResume() { + let (camera, writerMock, adaptorMock, inputMock, connectionMock) = createCamera() + + let videoSample = FLTCreateTestSampleBuffer().takeRetainedValue() + let audioSample = FLTCreateTestAudioSampleBuffer().takeRetainedValue() + + var status = AVAssetWriter.Status.unknown + writerMock.startWritingStub = { + status = .writing + } + writerMock.statusStub = { + return status + } + + var videoAppended = false + adaptorMock.appendPixelBufferStub = { buffer, time in + XCTAssert(CMTIME_IS_NUMERIC(time)) + videoAppended = true + return true + } + + var audioAppended = false + inputMock.readyForMoreMediaData = true + inputMock.appendSampleBufferStub = { buffer in + let sampleTime = CMSampleBufferGetPresentationTimeStamp(buffer!) + XCTAssert(CMTIME_IS_NUMERIC(sampleTime)) + audioAppended = true + return true + } + + camera.startVideoRecording(completion: { error in }, messengerForStreaming: nil) + camera.pauseVideoRecording() + camera.resumeVideoRecording() + camera.captureOutput( + camera.captureVideoOutput, didOutputSampleBuffer: videoSample, from: connectionMock) + camera.captureOutput(nil, didOutputSampleBuffer: audioSample, from: connectionMock) + camera.captureOutput( + camera.captureVideoOutput, didOutputSampleBuffer: videoSample, from: connectionMock) + camera.captureOutput(nil, didOutputSampleBuffer: audioSample, from: connectionMock) + + XCTAssert(videoAppended && audioAppended, "Video or audio was not appended.") + } + + func testDidOutputSampleBufferMustNotAppendSampleWhenReadyForMoreMediaDataIsFalse() { + let (camera, writerMock, adaptorMock, inputMock, connectionMock) = createCamera() + + let videoSample = FLTCreateTestSampleBuffer().takeRetainedValue() + + var sampleAppended = false + adaptorMock.appendPixelBufferStub = { buffer, time in + sampleAppended = true + return true + } + + camera.startVideoRecording(completion: { error in }, messengerForStreaming: nil) + + inputMock.readyForMoreMediaData = true + sampleAppended = false + camera.captureOutput( + camera.captureVideoOutput, didOutputSampleBuffer: videoSample, from: connectionMock) + XCTAssertTrue(sampleAppended, "Sample was not appended.") + + inputMock.readyForMoreMediaData = false + sampleAppended = false + camera.captureOutput( + camera.captureVideoOutput, didOutputSampleBuffer: videoSample, from: connectionMock) + XCTAssertFalse(sampleAppended, "Sample cannot be appended when readyForMoreMediaData is NO.") + } + + func testStopVideoRecordingWithCompletionMustCallCompletion() { + let (camera, writerMock, adaptorMock, inputMock, _) = createCamera() + + var status = AVAssetWriter.Status.unknown + writerMock.startWritingStub = { + status = .writing + } + writerMock.statusStub = { + return status + } + writerMock.finishWritingStub = { handler in + XCTAssert( + writerMock.status == .writing, + "Cannot call finishWritingWithCompletionHandler when status is not AVAssetWriter.Status.writing." + ) + handler?() + } + + camera.startVideoRecording(completion: { error in }, messengerForStreaming: nil) + var completionCalled = false + camera.stopVideoRecording(completion: { path, error in + completionCalled = true + }) + + XCTAssert(completionCalled, "Completion was not called.") + } + + func testStartWritingShouldNotBeCalledBetweenSampleCreationAndAppending() { + let (camera, writerMock, adaptorMock, inputMock, connectionMock) = createCamera() + + let videoSample = FLTCreateTestSampleBuffer().takeRetainedValue() + + var startWritingCalled = false + writerMock.startWritingStub = { + startWritingCalled = true + } + + var videoAppended = false + adaptorMock.appendPixelBufferStub = { buffer, time in + videoAppended = true + return true + } + + inputMock.readyForMoreMediaData = true + + camera.startVideoRecording(completion: { error in }, messengerForStreaming: nil) + + let startWritingCalledBefore = startWritingCalled + camera.captureOutput( + camera.captureVideoOutput, didOutputSampleBuffer: videoSample, from: connectionMock) + XCTAssert( + (startWritingCalledBefore && videoAppended) || (startWritingCalled && !videoAppended), + "The startWriting was called between sample creation and appending.") + + camera.captureOutput( + camera.captureVideoOutput, didOutputSampleBuffer: videoSample, from: connectionMock) + XCTAssert(videoAppended, "Video was not appended.") + } + + func testStartVideoRecordingWithCompletionShouldNotDisableMixWithOthers() { + let cam = FLTCreateCamWithCaptureSessionQueue(DispatchQueue(label: "testing")) + + try? AVAudioSession.sharedInstance().setCategory(.playback, options: .mixWithOthers) + cam.startVideoRecording(completion: { error in }, messengerForStreaming: nil) + XCTAssert( + AVAudioSession.sharedInstance().categoryOptions.contains(.mixWithOthers), + "Flag MixWithOthers was removed.") + XCTAssert( + AVAudioSession.sharedInstance().category == .playAndRecord, + "Category should be PlayAndRecord.") + } +} diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/SavePhotoDelegateTests.swift b/packages/camera/camera_avfoundation/example/ios/RunnerTests/SavePhotoDelegateTests.swift index 02a927557aa7..0e4c2e00441f 100644 --- a/packages/camera/camera_avfoundation/example/ios/RunnerTests/SavePhotoDelegateTests.swift +++ b/packages/camera/camera_avfoundation/example/ios/RunnerTests/SavePhotoDelegateTests.swift @@ -37,10 +37,8 @@ final class SavePhotoDelegateTests: XCTestCase { } let mockWritableData = MockWritableData() - mockWritableData.writeToFileStub = { path, options, error in - // TODO(FirentisTFW) Throw an error instead when migrating FLTWritableData to Swift - error?.pointee = ioError - return false + mockWritableData.writeToFileStub = { path, options in + throw ioError } delegate.handlePhotoCaptureResult(error: nil) { mockWritableData } @@ -60,9 +58,7 @@ final class SavePhotoDelegateTests: XCTestCase { } let mockWritableData = MockWritableData() - mockWritableData.writeToFileStub = { path, options, error in - return true - } + mockWritableData.writeToFileStub = { path, options in } delegate.handlePhotoCaptureResult(error: nil) { mockWritableData } @@ -80,11 +76,10 @@ final class SavePhotoDelegateTests: XCTestCase { ioQueue.setSpecific(key: ioQueueSpecific, value: ()) let mockWritableData = MockWritableData() - mockWritableData.writeToFileStub = { path, options, error in + mockWritableData.writeToFileStub = { path, options in if DispatchQueue.getSpecific(key: ioQueueSpecific) != nil { writeFileQueueExpectation.fulfill() } - return true } let filePath = "test" diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/StreamingTest.m b/packages/camera/camera_avfoundation/example/ios/RunnerTests/StreamingTest.m deleted file mode 100644 index a3786b2079da..000000000000 --- a/packages/camera/camera_avfoundation/example/ios/RunnerTests/StreamingTest.m +++ /dev/null @@ -1,108 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -@import camera_avfoundation; -#if __has_include() -@import camera_avfoundation.Test; -#endif -@import XCTest; -@import AVFoundation; - -#import "CameraTestUtils.h" -#import "MockFlutterBinaryMessenger.h" - -@interface MockImageStreamHandler : FLTImageStreamHandler -@property(nonatomic, copy) void (^eventSinkStub)(id event); -@end - -@implementation MockImageStreamHandler - -- (FlutterEventSink)eventSink { - if (self.eventSinkStub) { - return ^(id event) { - self.eventSinkStub(event); - }; - } - return nil; -} - -@end - -@interface StreamingTests : XCTestCase -@property(readonly, nonatomic) FLTCam *camera; -@property(readonly, nonatomic) CMSampleBufferRef sampleBuffer; -@end - -@implementation StreamingTests - -- (void)setUp { - dispatch_queue_t captureSessionQueue = dispatch_queue_create("testing", NULL); - FLTCamConfiguration *configuration = FLTCreateTestCameraConfiguration(); - configuration.captureSessionQueue = captureSessionQueue; - - _camera = FLTCreateCamWithConfiguration(configuration); - _sampleBuffer = FLTCreateTestSampleBuffer(); -} - -- (void)tearDown { - CFRelease(_sampleBuffer); -} - -- (void)testExceedMaxStreamingPendingFramesCount { - XCTestExpectation *streamingExpectation = [self - expectationWithDescription:@"Must not call handler over maxStreamingPendingFramesCount"]; - - MockImageStreamHandler *handlerMock = [[MockImageStreamHandler alloc] init]; - handlerMock.eventSinkStub = ^(id event) { - [streamingExpectation fulfill]; - }; - - MockFlutterBinaryMessenger *messenger = [[MockFlutterBinaryMessenger alloc] init]; - [_camera startImageStreamWithMessenger:messenger imageStreamHandler:handlerMock]; - - XCTKVOExpectation *expectation = [[XCTKVOExpectation alloc] initWithKeyPath:@"isStreamingImages" - object:_camera - expectedValue:@YES]; - XCTWaiterResult result = [XCTWaiter waitForExpectations:@[ expectation ] timeout:1]; - XCTAssertEqual(result, XCTWaiterResultCompleted); - - streamingExpectation.expectedFulfillmentCount = 4; - for (int i = 0; i < 10; i++) { - [_camera captureOutput:nil didOutputSampleBuffer:self.sampleBuffer fromConnection:nil]; - } - - [self waitForExpectationsWithTimeout:30 handler:nil]; -} - -- (void)testReceivedImageStreamData { - XCTestExpectation *streamingExpectation = - [self expectationWithDescription: - @"Must be able to call the handler again when receivedImageStreamData is called"]; - - MockImageStreamHandler *handlerMock = [[MockImageStreamHandler alloc] init]; - handlerMock.eventSinkStub = ^(id event) { - [streamingExpectation fulfill]; - }; - - MockFlutterBinaryMessenger *messenger = [[MockFlutterBinaryMessenger alloc] init]; - [_camera startImageStreamWithMessenger:messenger imageStreamHandler:handlerMock]; - - XCTKVOExpectation *expectation = [[XCTKVOExpectation alloc] initWithKeyPath:@"isStreamingImages" - object:_camera - expectedValue:@YES]; - XCTWaiterResult result = [XCTWaiter waitForExpectations:@[ expectation ] timeout:1]; - XCTAssertEqual(result, XCTWaiterResultCompleted); - - streamingExpectation.expectedFulfillmentCount = 5; - for (int i = 0; i < 10; i++) { - [_camera captureOutput:nil didOutputSampleBuffer:self.sampleBuffer fromConnection:nil]; - } - - [_camera receivedImageStreamData]; - [_camera captureOutput:nil didOutputSampleBuffer:self.sampleBuffer fromConnection:nil]; - - [self waitForExpectationsWithTimeout:30 handler:nil]; -} - -@end diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/StreamingTests.swift b/packages/camera/camera_avfoundation/example/ios/RunnerTests/StreamingTests.swift new file mode 100644 index 000000000000..2acae46f983c --- /dev/null +++ b/packages/camera/camera_avfoundation/example/ios/RunnerTests/StreamingTests.swift @@ -0,0 +1,91 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import AVFoundation +import XCTest + +@testable import camera_avfoundation + +private class MockImageStreamHandler: FLTImageStreamHandler { + var eventSinkStub: ((Any?) -> Void)? + + override var eventSink: FlutterEventSink? { + get { + if let stub = eventSinkStub { + return { event in + stub(event) + } + } + return nil + } + set { + eventSinkStub = newValue + } + } + +} + +final class StreamingTests: XCTestCase { + private func createCamera() -> (FLTCam, CMSampleBuffer) { + let captureSessionQueue = DispatchQueue(label: "testing") + let configuration = FLTCreateTestCameraConfiguration() + configuration.captureSessionQueue = captureSessionQueue + + let camera = FLTCreateCamWithConfiguration(configuration) + let sampleBuffer = FLTCreateTestSampleBuffer().takeRetainedValue() + + return (camera, sampleBuffer) + } + + func testExceedMaxStreamingPendingFramesCount() { + let (camera, sampleBuffer) = createCamera() + let streamingExpectation = expectation( + description: "Must not call handler over maxStreamingPendingFramesCount") + let handlerMock = MockImageStreamHandler() + handlerMock.eventSinkStub = { event in + streamingExpectation.fulfill() + } + let messenger = MockFlutterBinaryMessenger() + camera.startImageStream(with: messenger, imageStreamHandler: handlerMock) + + let expectation = XCTKVOExpectation( + keyPath: "isStreamingImages", object: camera, expectedValue: true) + let result = XCTWaiter.wait(for: [expectation], timeout: 1) + XCTAssertEqual(result, .completed) + + streamingExpectation.expectedFulfillmentCount = 4 + for _ in 0..<10 { + camera.captureOutput(nil, didOutputSampleBuffer: sampleBuffer, from: nil) + } + + waitForExpectations(timeout: 30, handler: nil) + } + + func testReceivedImageStreamData() { + let (camera, sampleBuffer) = createCamera() + let streamingExpectation = expectation( + description: "Must be able to call the handler again when receivedImageStreamData is called") + let handlerMock = MockImageStreamHandler() + handlerMock.eventSinkStub = { event in + streamingExpectation.fulfill() + } + let messenger = MockFlutterBinaryMessenger() + camera.startImageStream(with: messenger, imageStreamHandler: handlerMock) + + let expectation = XCTKVOExpectation( + keyPath: "isStreamingImages", object: camera, expectedValue: true) + let result = XCTWaiter.wait(for: [expectation], timeout: 1) + XCTAssertEqual(result, .completed) + + streamingExpectation.expectedFulfillmentCount = 5 + for _ in 0..<10 { + camera.captureOutput(nil, didOutputSampleBuffer: sampleBuffer, from: nil) + } + + camera.receivedImageStreamData() + camera.captureOutput(nil, didOutputSampleBuffer: sampleBuffer, from: nil) + + waitForExpectations(timeout: 30, handler: nil) + } +} diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/ThreadSafeEventChannelTests.m b/packages/camera/camera_avfoundation/example/ios/RunnerTests/ThreadSafeEventChannelTests.m deleted file mode 100644 index 7c845892d55e..000000000000 --- a/packages/camera/camera_avfoundation/example/ios/RunnerTests/ThreadSafeEventChannelTests.m +++ /dev/null @@ -1,92 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -@import camera_avfoundation; -#if __has_include() -@import camera_avfoundation.Test; -#endif -@import XCTest; - -#import "MockEventChannel.h" - -@interface ThreadSafeEventChannelTests : XCTestCase - -@end - -@implementation ThreadSafeEventChannelTests - -- (void)testSetStreamHandler_shouldStayOnMainThreadIfCalledFromMainThread { - MockEventChannel *mockEventChannel = [[MockEventChannel alloc] init]; - FLTThreadSafeEventChannel *threadSafeEventChannel = - [[FLTThreadSafeEventChannel alloc] initWithEventChannel:mockEventChannel]; - - XCTestExpectation *mainThreadExpectation = - [self expectationWithDescription:@"setStreamHandler must be called on the main thread"]; - XCTestExpectation *mainThreadCompletionExpectation = - [self expectationWithDescription: - @"setStreamHandler's completion block must be called on the main thread"]; - - [mockEventChannel setSetStreamHandlerStub:^(NSObject *handler) { - if (NSThread.isMainThread) { - [mainThreadExpectation fulfill]; - } - }]; - - [threadSafeEventChannel setStreamHandler:nil - completion:^{ - if (NSThread.isMainThread) { - [mainThreadCompletionExpectation fulfill]; - } - }]; - [self waitForExpectationsWithTimeout:30 handler:nil]; -} - -- (void)testSetStreamHandler_shouldDispatchToMainThreadIfCalledFromBackgroundThread { - MockEventChannel *mockEventChannel = [[MockEventChannel alloc] init]; - FLTThreadSafeEventChannel *threadSafeEventChannel = - [[FLTThreadSafeEventChannel alloc] initWithEventChannel:mockEventChannel]; - - XCTestExpectation *mainThreadExpectation = - [self expectationWithDescription:@"setStreamHandler must be called on the main thread"]; - XCTestExpectation *mainThreadCompletionExpectation = - [self expectationWithDescription: - @"setStreamHandler's completion block must be called on the main thread"]; - - [mockEventChannel setSetStreamHandlerStub:^(NSObject *handler) { - if (NSThread.isMainThread) { - [mainThreadExpectation fulfill]; - } - }]; - - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - [threadSafeEventChannel setStreamHandler:nil - completion:^{ - if (NSThread.isMainThread) { - [mainThreadCompletionExpectation fulfill]; - } - }]; - }); - [self waitForExpectationsWithTimeout:30 handler:nil]; -} - -- (void)testEventChannel_shouldBeKeptAliveWhenDispatchingBackToMainThread { - MockEventChannel *mockEventChannel = [[MockEventChannel alloc] init]; - - XCTestExpectation *expectation = - [self expectationWithDescription:@"Completion should be called."]; - - dispatch_async(dispatch_queue_create("test", NULL), ^{ - FLTThreadSafeEventChannel *channel = - [[FLTThreadSafeEventChannel alloc] initWithEventChannel:mockEventChannel]; - - [channel setStreamHandler:nil - completion:^{ - [expectation fulfill]; - }]; - }); - - [self waitForExpectationsWithTimeout:30 handler:nil]; -} - -@end diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/ThreadSafeEventChannelTests.swift b/packages/camera/camera_avfoundation/example/ios/RunnerTests/ThreadSafeEventChannelTests.swift new file mode 100644 index 000000000000..5e0790d22f33 --- /dev/null +++ b/packages/camera/camera_avfoundation/example/ios/RunnerTests/ThreadSafeEventChannelTests.swift @@ -0,0 +1,73 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import XCTest + +@testable import camera_avfoundation + +final class ThreadSafeEventChannelTests: XCTestCase { + func testSetStreamHandler_shouldStayOnMainThreadIfCalledFromMainThread() { + let mockEventChannel = MockEventChannel() + let threadSafeEventChannel = FLTThreadSafeEventChannel(eventChannel: mockEventChannel) + + let mainThreadExpectation = expectation( + description: "setStreamHandler must be called on the main thread") + let mainThreadCompletionExpectation = expectation( + description: "setStreamHandler's completion block must be called on the main thread") + + mockEventChannel.setStreamHandlerStub = { handler in + if Thread.isMainThread { + mainThreadExpectation.fulfill() + } + } + threadSafeEventChannel.setStreamHandler(nil) { + if Thread.isMainThread { + mainThreadCompletionExpectation.fulfill() + } + } + + waitForExpectations(timeout: 30, handler: nil) + } + + func testSetStreamHandler_shouldDispatchToMainThreadIfCalledFromBackgroundThread() { + let mockEventChannel = MockEventChannel() + let threadSafeEventChannel = FLTThreadSafeEventChannel(eventChannel: mockEventChannel) + + let mainThreadExpectation = expectation( + description: "setStreamHandler must be called on the main thread") + let mainThreadCompletionExpectation = expectation( + description: "setStreamHandler's completion block must be called on the main thread") + + mockEventChannel.setStreamHandlerStub = { handler in + if Thread.isMainThread { + mainThreadExpectation.fulfill() + } + } + DispatchQueue.global(qos: .default).async { + threadSafeEventChannel.setStreamHandler(nil) { + if Thread.isMainThread { + mainThreadCompletionExpectation.fulfill() + } + } + } + + waitForExpectations(timeout: 30, handler: nil) + } + + func testEventChannel_shouldBeKeptAliveWhenDispatchingBackToMainThread() { + let mockEventChannel = MockEventChannel() + + let expectation = self.expectation(description: "Completion should be called.") + + DispatchQueue(label: "test").async { + let channel = FLTThreadSafeEventChannel(eventChannel: mockEventChannel) + + channel.setStreamHandler(nil) { + expectation.fulfill() + } + } + + waitForExpectations(timeout: 30, handler: nil) + } +}