PartsRepresentableTests.swift 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175
  1. // Copyright 2024 Google LLC
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. import CoreGraphics
  15. import XCTest
  16. #if canImport(UIKit)
  17. import UIKit
  18. #elseif canImport(AppKit)
  19. import AppKit
  20. #endif
  21. #if canImport(CoreImage)
  22. import CoreImage
  23. #endif // canImport(CoreImage)
  24. @testable import FirebaseAI
  25. @available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *)
  26. final class PartsRepresentableTests: XCTestCase {
  27. #if !os(watchOS)
  28. func testModelContentFromCGImageIsNotEmpty() throws {
  29. // adapted from https://forums.swift.org/t/creating-a-cgimage-from-color-array/18634/2
  30. var srgbArray = [UInt32](repeating: 0xFFFF_FFFF, count: 8 * 8)
  31. let image = srgbArray.withUnsafeMutableBytes { ptr -> CGImage in
  32. let ctx = CGContext(
  33. data: ptr.baseAddress,
  34. width: 8,
  35. height: 8,
  36. bitsPerComponent: 8,
  37. bytesPerRow: 4 * 8,
  38. space: CGColorSpace(name: CGColorSpace.sRGB)!,
  39. bitmapInfo: CGBitmapInfo.byteOrder32Little.rawValue +
  40. CGImageAlphaInfo.premultipliedFirst.rawValue
  41. )!
  42. return ctx.makeImage()!
  43. }
  44. let modelContent = image.partsValue
  45. XCTAssert(modelContent.count > 0, "Expected non-empty model content for CGImage: \(image)")
  46. }
  47. #endif // !os(watchOS)
  48. #if canImport(CoreImage)
  49. func testModelContentFromCIImageIsNotEmpty() throws {
  50. let image = CIImage(color: CIColor.red)
  51. .cropped(to: CGRect(origin: CGPointZero, size: CGSize(width: 16, height: 16)))
  52. let modelContent = image.partsValue
  53. XCTAssert(modelContent.count > 0, "Expected non-empty model content for CGImage: \(image)")
  54. }
  55. func testModelContentFromInvalidCIImageThrows() throws {
  56. let image = CIImage.empty()
  57. let modelContent = image.partsValue
  58. let part = try XCTUnwrap(modelContent.first)
  59. let errorPart = try XCTUnwrap(part as? ErrorPart, "Expected ErrorPart.")
  60. let imageError = try XCTUnwrap(
  61. errorPart.error as? ImageConversionError,
  62. "Got unexpected error type: \(errorPart.error)"
  63. )
  64. guard case .couldNotConvertToJPEG = imageError else {
  65. XCTFail("Expected JPEG conversion error, got \(imageError) instead.")
  66. return
  67. }
  68. }
  69. #endif // canImport(CoreImage)
  70. #if canImport(UIKit) && !os(visionOS) // These tests are stalling in CI on visionOS.
  71. func testModelContentFromInvalidUIImageThrows() throws {
  72. let image = UIImage()
  73. let modelContent = image.partsValue
  74. let part = try XCTUnwrap(modelContent.first)
  75. let errorPart = try XCTUnwrap(part as? ErrorPart, "Expected ErrorPart.")
  76. let imageError = try XCTUnwrap(
  77. errorPart.error as? ImageConversionError,
  78. "Got unexpected error type: \(errorPart.error)"
  79. )
  80. guard case .couldNotConvertToJPEG = imageError else {
  81. XCTFail("Expected JPEG conversion error, got \(imageError) instead.")
  82. return
  83. }
  84. }
  85. func testModelContentFromUIImageIsNotEmpty() throws {
  86. let image = try XCTUnwrap(UIImage(systemName: "star.fill"))
  87. let modelContent = image.partsValue
  88. XCTAssert(modelContent.count > 0, "Expected non-empty model content for UIImage: \(image)")
  89. }
  90. #elseif canImport(AppKit)
  91. func testModelContentFromNSImageIsNotEmpty() throws {
  92. let coreImage = CIImage(color: CIColor.red)
  93. .cropped(to: CGRect(origin: CGPointZero, size: CGSize(width: 16, height: 16)))
  94. let rep = NSCIImageRep(ciImage: coreImage)
  95. let image = NSImage(size: rep.size)
  96. image.addRepresentation(rep)
  97. let modelContent = image.partsValue
  98. XCTAssert(modelContent.count > 0, "Expected non-empty model content for NSImage: \(image)")
  99. }
  100. func testModelContentFromInvalidNSImageThrows() throws {
  101. let image = NSImage()
  102. let modelContent = image.partsValue
  103. let part = try XCTUnwrap(modelContent.first)
  104. let errorPart = try XCTUnwrap(part as? ErrorPart, "Expected ErrorPart.")
  105. let imageError = try XCTUnwrap(
  106. errorPart.error as? ImageConversionError,
  107. "Got unexpected error type: \(errorPart.error)"
  108. )
  109. guard case .invalidUnderlyingImage = imageError else {
  110. XCTFail("Expected invalid underlying image conversion error, got \(imageError) instead.")
  111. return
  112. }
  113. }
  114. #endif
  115. func testMixedParts() throws {
  116. let text = "This is a test"
  117. let data = try XCTUnwrap("This is some data".data(using: .utf8))
  118. let inlineData = InlineDataPart(data: data, mimeType: "text/plain")
  119. let parts: [any PartsRepresentable] = [text, inlineData]
  120. let modelContent = ModelContent(parts: parts)
  121. XCTAssertEqual(modelContent.parts.count, 2)
  122. let textPart = try XCTUnwrap(modelContent.parts[0] as? TextPart)
  123. XCTAssertEqual(textPart.text, text)
  124. let dataPart = try XCTUnwrap(modelContent.parts[1] as? InlineDataPart)
  125. XCTAssertEqual(dataPart, inlineData)
  126. }
  127. #if canImport(UIKit)
  128. func testMixedParts_withImage() throws {
  129. let text = "This is a test"
  130. let image = try XCTUnwrap(UIImage(systemName: "star"))
  131. let parts: [any PartsRepresentable] = [text, image]
  132. let modelContent = ModelContent(parts: parts)
  133. XCTAssertEqual(modelContent.parts.count, 2)
  134. let textPart = try XCTUnwrap(modelContent.parts[0] as? TextPart)
  135. XCTAssertEqual(textPart.text, text)
  136. let imagePart = try XCTUnwrap(modelContent.parts[1] as? InlineDataPart)
  137. XCTAssertEqual(imagePart.mimeType, "image/jpeg")
  138. XCTAssertFalse(imagePart.data.isEmpty)
  139. }
  140. #elseif canImport(AppKit)
  141. func testMixedParts_withImage() throws {
  142. let text = "This is a test"
  143. let coreImage = CIImage(color: CIColor.blue)
  144. .cropped(to: CGRect(origin: CGPoint.zero, size: CGSize(width: 16, height: 16)))
  145. let rep = NSCIImageRep(ciImage: coreImage)
  146. let image = NSImage(size: rep.size)
  147. image.addRepresentation(rep)
  148. let parts: [any PartsRepresentable] = [text, image]
  149. let modelContent = ModelContent(parts: parts)
  150. XCTAssertEqual(modelContent.parts.count, 2)
  151. let textPart = try XCTUnwrap(modelContent.parts[0] as? TextPart)
  152. XCTAssertEqual(textPart.text, text)
  153. let imagePart = try XCTUnwrap(modelContent.parts[1] as? InlineDataPart)
  154. XCTAssertEqual(imagePart.mimeType, "image/jpeg")
  155. XCTAssertFalse(imagePart.data.isEmpty)
  156. }
  157. #endif
  158. }