// RUN: %empty-directory(%t)
// RUN: %target-build-swift -emit-library -o %t/libGetImageNameHelper.dylib -emit-module %S/Inputs/class_getImageName-helper.swift -Xlinker -install_name -Xlinker @executable_path/libGetImageNameHelper.dylib
// RUN: %target-codesign %t/libGetImageNameHelper.dylib

// RUN: %target-build-swift -g %s -I %t -o %t/main -L %t -lGetImageNameHelper
// RUN: %target-codesign %t/main
// RUN: %target-run %t/main %t/libGetImageNameHelper.dylib

// REQUIRES: executable_test
// REQUIRES: objc_interop

import Darwin
import ObjectiveC
import GetImageNameHelper
import StdlibUnittest

func check(_ cls: AnyClass, in library: String) {
  guard let imageName = class_getImageName(cls) else {
    expectUnreachable("could not find image for \(cls)")
    return
  }
  expectTrue(String(cString: imageName).hasSuffix(library),
             "wrong library for \(cls)")
}

var newOSButCannotUseObjCRuntimeHook = false
if #available(macOS 10.14, iOS 12, tvOS 12, watchOS 5, *) {
  // The only place these tests will fail is on early versions of the 2018 OSs.
  // The final versions will have 'objc_setHook_getImageName'; anything earlier
  // will be handled by manually overwriting the original implementation of
  // 'class_getImageName'.
  newOSButCannotUseObjCRuntimeHook =
      (nil == dlsym(UnsafeMutableRawPointer(bitPattern: -2),
                    "objc_setHook_getImageName"))
}

var testSuite = TestSuite("class_getImageName")

testSuite.test("Simple") {
  check(SimpleSwiftObject.self, in: "libGetImageNameHelper.dylib")
  check(SimpleNSObject.self, in: "libGetImageNameHelper.dylib")
}

testSuite.test("Generic")
    .skip(.custom({ newOSButCannotUseObjCRuntimeHook },
                   reason: "hook for class_getImageName not present"))
    .code {
  check(GenericSwiftObject<Int>.self, in: "libGetImageNameHelper.dylib")
  check(GenericSwiftObject<NSObject>.self, in: "libGetImageNameHelper.dylib")

  check(GenericNSObject<Int>.self, in: "libGetImageNameHelper.dylib")
  check(GenericNSObject<NSObject>.self, in: "libGetImageNameHelper.dylib")
}

testSuite.test("GenericAncestry")
    .skip(.custom({ newOSButCannotUseObjCRuntimeHook },
                   reason: "hook for class_getImageName not present"))
    .code {
  check(GenericAncestrySwiftObject.self, in: "libGetImageNameHelper.dylib")
  check(GenericAncestryNSObject.self, in: "libGetImageNameHelper.dylib")
}

testSuite.test("Resilient") {
  check(ResilientFieldSwiftObject.self, in: "libGetImageNameHelper.dylib")
  check(ResilientFieldNSObject.self, in: "libGetImageNameHelper.dylib")
}

testSuite.test("ObjC") {
  check(NSObject.self, in: "libobjc.A.dylib")
}

testSuite.test("KVO/Simple") {
  // We use object_getClass in this test to not look through KVO's artificial
  // subclass.
  let obj = SimpleNSObject()
  autoreleasepool {
    let observation = obj.observe(\.observableName) { _, _ in }
    withExtendedLifetime(observation) {
      let theClass = object_getClass(obj)
      precondition(theClass !== SimpleNSObject.self, "no KVO subclass?")
      expectNil(class_getImageName(theClass),
                "should match what happens with NSObject (below)")
    }
  }
}

testSuite.test("KVO/GenericAncestry") {
  // We use object_getClass in this test to not look through KVO's artificial
  // subclass.
  let obj = GenericAncestryNSObject()
  autoreleasepool {
    let observation = obj.observe(\.observableName) { _, _ in }
    withExtendedLifetime(observation) {
      let theClass = object_getClass(obj)
      precondition(theClass !== GenericAncestryNSObject.self, "no KVO subclass?")
      expectNil(class_getImageName(theClass),
                "should match what happens with NSObject (below)")
    }
  }
}

testSuite.test("KVO/ObjC") {
  // We use object_getClass in this test to not look through KVO's artificial
  // subclass.
  let obj = NSObject()
  autoreleasepool {
    let observation = obj.observe(\.description) { _, _ in }
    withExtendedLifetime(observation) {
      let theClass = object_getClass(obj)
      precondition(theClass !== NSObject.self, "no KVO subclass?")
      expectNil(class_getImageName(theClass),
                "should match what happens with the Swift objects (above)")
    }
  }
}

testSuite.test("dynamic") {
  let newClass: AnyClass = objc_allocateClassPair(/*superclass*/NSObject.self,
                                                  "CompletelyDynamic",
                                                  /*extraBytes*/0)!
  objc_registerClassPair(newClass)

  // We don't actually care what the result is; we just need to not crash.
  _ = class_getImageName(newClass)
}

testSuite.test("nil") {
  // The ObjC runtime should handle this before it even gets to Swift's custom
  // implementation, but just in case.
  expectNil(class_getImageName(nil))
}

runAllTests()
