;;;; Objective-CL, an Objective-C bridge for Common Lisp. ;;;; Copyright (C) 2007 Matthias Andreas Benkard. ;;;; ;;;; This program is free software: you can redistribute it and/or ;;;; modify it under the terms of the GNU Lesser General Public License ;;;; as published by the Free Software Foundation, either version 3 of ;;;; the License, or (at your option) any later version. ;;;; ;;;; This program is distributed in the hope that it will be useful, but ;;;; WITHOUT ANY WARRANTY; without even the implied warranty of ;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ;;;; Lesser General Public License for more details. ;;;; ;;;; You should have received a copy of the GNU Lesser General Public ;;;; License along with this program. If not, see ;;;; . (defpackage #:mulk.objective-cl.tests (:nicknames #:objcl-tests #:objective-cl-tests #:mulk.objcl-tests) (:use #:stefil #:mulk.objective-cl #:cl) (:export #:run-all-tests #:objective-cl) (:shadowing-import-from #:objcl #:struct #:union #:pointer #:oneway #:out #:in #:inout #:const #:parse-typespec #:objective-c-class #:bit-field #:opaque #:bycopy #:byref #:primitive-invoke #:print-typespec-to-string #:nominally #:find-objc-meta-class #:objcl-object-backed-by-lisp-class-p #:foreign-class-registered-p #:define-objective-c-method #:defobjcmethod #:objective-c-generic-function #:objective-c-method #:+nil+ #:+yes+ #:+no+ #:selector #:typespec-primary-type)) (in-package #:mulk.objective-cl.tests) (in-root-suite) (defun run-all-tests () (objective-cl)) (defsuite objective-cl () (objcl:initialise-runtime) (run-child-tests) (objcl:shutdown-runtime)) (in-suite objective-cl) #.(prog1 nil (defparameter *readtable-backup* *readtable*) (setq *readtable* (copy-readtable)) (setf (readtable-case *readtable*) :invert)) (deftest base-functions () (is (objc-equal (find-objc-class 'ns-object) (find-objc-class "NSObject"))) (is (null (find-objc-class 'nsobject))) (is (objc-equal (find-objc-class 'ns-method-invocation) (find-objc-class "NSMethodInvocation"))) (is (typep (find-selector "mulkyStuff:withMagic:" nil) '(or null selector))) (is (objc-equal (find-selector "self") (find-selector '(self)))) (is (objc-equal (find-selector "stringWithUTF8String:") (find-selector '(:string-with-u-t-f-8-string)))) (is (objc-equal (find-selector "stringWithCString:encoding:") (find-selector '(:string-with-c-string :encoding)))) ;; Case-sensitivity. (is (objc-equal (find-selector "stringWithCString:encoding:") (find-selector '(:stringWithCString :encoding)))) (is (objc-equal (find-selector "stringWithUTF8String:") (find-selector :stringWithUTF8String)))) #.(prog1 nil (setq *readtable* *readtable-backup*)) (deftest primitive-method-invocation () (#+(or) (signals error (primitive-invoke (find-objc-class 'ns-object) 'string 'id))) (signals error (primitive-invoke 300 'self 'id)) (signals error (primitive-invoke "abc" 'self 'id)) (#+(or) (signals error (primitive-invoke (find-objc-class 'ns-object) 'selph 'id))) (is (objc-equal (primitive-invoke (find-objc-class 'ns-object) 'self 'id) (primitive-invoke (find-objc-class 'ns-object) 'class 'objective-c-class))) (is (not (objc-equal (primitive-invoke (find-objc-class 'ns-object) 'self 'id) (primitive-invoke (find-objc-class 'ns-number) 'self 'id)))) (is (objc-equal (primitive-invoke (find-objc-class 'ns-string) :string-with-u-t-f-8-string 'id "Mulk.") (primitive-invoke (find-objc-class 'ns-string) :string-with-u-t-f-8-string 'id "Mulk."))) (is (not (objc-equal (primitive-invoke (find-objc-class 'ns-string) :string-with-u-t-f-8-string 'id "Mulk.") (primitive-invoke (find-objc-class 'ns-string) :string-with-u-t-f-8-string 'id "Klum.")))) (is (primitive-invoke (find-objc-class 'ns-string) :is-subclass-of-class (typespec-primary-type (parse-typespec "c" t)) (find-objc-class 'ns-object))) ;; performSelector:withObject: cannot be used with non-id return ;; types. #+(or) (is (primitive-invoke (find-objc-class 'ns-string) '(:perform-selector :with-object) :char (selector "isSubclassOfClass:") (find-objc-class 'ns-object)))) #.(enable-objective-c-syntax) (deftest method-invocation () (signals error [NSObject 300]) (signals error [300 self]) (signals error ["abc" self]) (signals error [NSObject selph]) (is (objc-equal [NSObject self] [NSObject class])) (is (not (objc-equal [NSObject self] [NSNumber self]))) (is (objc-equal [NSString stringWithUTF8String: "Mulk."] [NSString stringWithUTF8String: "Mulk."])) (is (not (objc-equal [NSString stringWithUTF8String: "Mulk."] [NSString stringWithUTF8String: "Klum."]))) (is [NSString isSubclassOfClass: [NSObject class]]) ;; performSelector:withObject: cannot be used with non-id return ;; types. #+(or) (is [NSString performSelector: (selector "stringWithUTF8String:") withObject: [NSObject class]])) #.(disable-objective-c-syntax) (defun typespec-equalp (t1 t2) (equalp (objcl::typespec t1) (objcl::typespec t2))) (deftest parsing-typespecs () (is (typespec-equalp (parse-typespec "@0:4{_NSRange=II}8") '(id ()))) (is (typespec-equalp (parse-typespec ":4{_NSRange=II}8") '(selector ()))) (is (typespec-equalp (parse-typespec "{_NSRange=II}8") '(struct () "_NSRange" (:unsigned-int ()) (:unsigned-int ())))) (is (typespec-equalp (parse-typespec "rnNoV^V[10rjd]4") ;; Actually, the order of the qualifiers doesn't ;; matter, which means that this test is dumber than ;; it ought to be. '(pointer (oneway out inout in const) (array (oneway) 10 (complex (const) (:double nil)))))) (is (typespec-equalp (parse-typespec "ROi") ;; Here, too, the order of the qualifiers is irrelevant. '(:int (bycopy byref)))) (is (typespec-equalp (parse-typespec "(?=)") '(union () "?"))) (is (typespec-equalp (parse-typespec "{?=rb123rjf456iii}") (if (eq objcl::+runtime-type+ :gnu) '(struct () "?" (bit-field (const) 123 456 (complex (const) (:float ()))) (:int ()) (:int ()) (:int ())) '(struct () "?" (bit-field (const) nil 123) (complex (const) (:float ())) (:unrecognised () #\4) (:unrecognised () #\5) (:unrecognised () #\6) (:int ()) (:int ()) (:int ()))))) (is (typespec-equalp (parse-typespec "^[100{?=ii}]") '(pointer () (array () 100 (struct () "?" (:int ()) (:int ())))))) (is (typespec-equalp (parse-typespec "{?=BiIlLqQfd@#:*?}") '(struct () "?" (:boolean ()) (:int ()) (:unsigned-int ()) (:long ()) (:unsigned-long ()) (:long-long ()) (:unsigned-long-long ()) (:float ()) (:double ()) (id ()) (objective-c-class ()) (selector ()) (:string ()) (:unknown ())))) (let ((funky-spec (parse-typespec "{?=cC}"))) (is (member funky-spec '((struct () "?" (:char ()) (:unsigned-char ())) (struct () "?" (:int ((nominally :char))) (:unsigned-int ((nominally :unsigned-char))))) :test #'typespec-equalp))) (let ((funky-spec (parse-typespec "{?=sS}"))) (is (member funky-spec '((struct () "?" (:short ()) (:unsigned-short ())) (struct () "?" (:int ((nominally :short))) (:unsigned-int ((nominally :unsigned-short))))) :test #'typespec-equalp))) (is (typespec-equalp (parse-typespec "{Mulk=*{Untermulk={Unteruntermulk=}}i}") '(struct () "Mulk" (:string ()) (struct () "Untermulk" (struct () "Unteruntermulk")) (:int ())))) (is (typespec-equalp (parse-typespec "^^{OpaqueStruct}") '(pointer () (pointer () (struct (opaque) "OpaqueStruct"))))) (is (typespec-equalp (parse-typespec "^{_GSKeyBinding=ii@\"GSKeyBindingAction\"@\"GSKeyBindingTable\"}") '(pointer () (struct () "_GSKeyBinding" (:int ()) (:int ()) (id ((:type "GSKeyBindingAction"))) (id ((:type "GSKeyBindingTable"))))))) (is (typespec-equalp (parse-typespec "{?=\"next\"@\"GCObject\"\"previous\"@\"GCObject\"\"flags\"{?=}}") '(struct () "?" (id ((:type "GCObject") (:name "next"))) (id ((:type "GCObject") (:name "previous"))) (struct ((:name "flags")) "?"))))) (deftest printing-typespecs () (is (equal (print-typespec-to-string '(id ())) "@")) (is (equal (print-typespec-to-string '(selector ())) ":")) (is (equal (print-typespec-to-string '(struct () "_NSRange" (:unsigned-int ()) (:unsigned-int ()))) "{_NSRange=II}")) (is (equal (print-typespec-to-string '(pointer (oneway out inout in const) (array (oneway) 10 (complex (const) (:double nil))))) ;; Actually, the order of the qualifiers doesn't ;; matter, which means that this test is dumber than ;; it ought to be. "VoNnr^V[10rjd]")) (is (equal (print-typespec-to-string '(:int (bycopy byref))) ;; Here, too, the order of the qualifiers is irrelevant. "ORi")) (is (equal (print-typespec-to-string '(union () "?")) "(?=)")) (is (equal (print-typespec-to-string (if (eq objcl::+runtime-type+ :gnu) '(struct () "?" (bit-field (const) 123 456 (complex (const) (:float ()))) (:int ()) (:int ()) (:int ())) '(struct () "?" (bit-field (const) nil 123) (complex (const) (:float ())) (:unrecognised () #\4) (:unrecognised () #\5) (:unrecognised () #\6) (:int ()) (:int ()) (:int ())))) "{?=rb123rjf456iii}")) (is (equal (print-typespec-to-string '(pointer () (array () 100 (struct () "?" (:int ()) (:int ()))))) "^[100{?=ii}]")) (is (equal (print-typespec-to-string '(struct () "?" (:boolean ()) (:int ()) (:unsigned-int ()) (:long ()) (:unsigned-long ()) (:long-long ()) (:unsigned-long-long ()) (:float ()) (:double ()) (id ()) (objective-c-class ()) (selector ()) (:string ()) (:unknown ()))) "{?=BiIlLqQfd@#:*?}")) (is (equal (print-typespec-to-string '(struct () "?" (:char ()) (:unsigned-char ()))) "{?=cC}")) (is (equal (print-typespec-to-string '(struct () "?" (:short ()) (:unsigned-short ()))) "{?=sS}")) (is (equal (print-typespec-to-string '(struct () "Mulk" (:string ()) (struct () "Untermulk" (struct () "Unteruntermulk")) (:int ()))) "{Mulk=*{Untermulk={Unteruntermulk=}}i}")) (is (equal (print-typespec-to-string '(pointer () (pointer () (struct (opaque) "OpaqueStruct")))) "^^{OpaqueStruct}"))) #.(enable-objective-c-syntax) (deftest data-coercion () (is (objc-equal [NSString stringWithUTF8String: "Mulk."] [NSString stringWithCString: "Mulk." encoding: 4])) (is (objc-equal [NSString respondsToSelector: (selector "new")] [NSString respondsToSelector: 'new])) (is (objc-equal [NSString respondsToSelector: (selector "new")] [NSString respondsToSelector: "new"])) (is (typep [NSString isEqual: [NSString self]] 'boolean)) (is (typep [NSString isEqual: [NSObject self]] 'boolean))) #.(disable-objective-c-syntax) #.(enable-objective-c-syntax) (deftest numbers () (is (objc-equal [[NSDecimalNumber decimalNumberWithString: [NSString stringWithUTF8String: "-12345"]] doubleValue] -12345d0))) #.(disable-objective-c-syntax) #.(enable-objective-c-syntax) (deftest exception-handling () (is (typep (handler-case [NSString selph] (error (e) e)) '(or no-such-selector message-not-understood))) (is (typep (handler-case [NSObject string] (error (e) e)) 'message-not-understood))) #.(disable-objective-c-syntax) #.(enable-objective-c-syntax) (deftest reader-syntax () (is (objc-equal [NSObject self] (find-objc-class 'ns-object))) (is (objc-equal [NSString stringWithUTF8String: "Mulk."] (invoke (find-objc-class 'ns-string) :string-with-u-t-f-8-string "Mulk."))) (is (objc-equal [NSString stringWithCString: "Mulk." encoding: 4] (invoke (find-objc-class 'ns-string) :string-with-c-string "Mulk." :encoding 4))) ;; performSelector:withObject: cannot be used with non-id return ;; types. #+(or) (is (objc-equal [NSString performSelector: (selector "isSubclassOfClass:") withObject: [NSObject self]] (invoke (find-objc-class 'ns-string) :perform-selector (selector "isSubclassOfClass:") :with-object (invoke (find-objc-class 'ns-object) 'self))))) #.(disable-objective-c-syntax) #.(enable-method-syntax) (deftest method-syntax () (is (eq '#/stringWithCString:encoding: 'objective-c-selectors:|stringWithCString:encoding:|)) (is (fboundp '#/stringWithCString:encoding:)) (is (typep (fdefinition '#/stringWithCString:encoding:) 'selector)) (is (objc-equal (#/stringWithCString:encoding: (find-objc-class 'ns-string) "Mulk." 4) (invoke (find-objc-class 'ns-string) :string-with-c-string "Mulk." :encoding 4))) (is (objc-equal (#/self (find-objc-class 'ns-array)) (find-objc-class 'ns-array))) (is (subtypep (#/class (#/new (find-objc-class 'ns-mutable-array))) (find-objc-class 'ns-mutable-array)))) #.(disable-method-syntax) (deftest compiler-macros () (flet ((compiler-macroexpand-1 (form &optional environment) (funcall (or (compiler-macro-function (car form)) #'(lambda (x y) (declare (ignore y)) x)) form environment))) (is (equal (compiler-macroexpand-1 '(invoke x :is-equal y) nil) `(invoke-by-name x (selector '(:is-equal)) y))) (is (equal (compiler-macroexpand-1 '(invoke x 'self)) `(invoke-by-name x (selector '(self))))) (is (equal (compiler-macroexpand-1 `(invoke x :string-with-c-string "Mulk." :encoding 4)) `(invoke-by-name x (selector '(:string-with-c-string :encoding)) "Mulk." 4))) (is (equal (compiler-macroexpand-1 `(invoke-by-name (+ 1 2) 'self)) `(invoke-by-name (+ 1 2) (selector 'self)))) (is (equal (compiler-macroexpand-1 `(invoke-by-name (+ 1 2) '(:foo :bar) x y)) `(invoke-by-name (+ 1 2) (selector '(:foo :bar)) x y))) (is (equal (compiler-macroexpand-1 `(primitive-invoke (+ 1 2) '(:foo :bar) x y)) `(primitive-invoke (+ 1 2) (selector '(:foo :bar)) x y))) (is (equal (car (compiler-macroexpand-1 `(selector '(:foo :bar)))) 'load-time-value)) (is (not (equal (car (compiler-macroexpand-1 `(selector (:foo :bar)))) 'load-time-value))) (is (not (equal (car (compiler-macroexpand-1 `(selector (car (:foo :bar))))) 'load-time-value))) (is (not (equal (car (compiler-macroexpand-1 `(selector `(,x ,y)))) 'load-time-value))))) (defvar *class-counter* 0) (deftest class-definition () (let ((class-name (intern (format nil "~A~D" '#:mlk-test-string (incf *class-counter*)))) (numclass-name (intern (format nil "~A~D" '#:mlk-test-number *class-counter*) '#:objective-c-classes)) (subnumclass-name (intern (format nil "~A~D" '#:mlk-test-sub-number *class-counter*) '#:objective-c-classes)) (class nil) (instance nil) (numinstance nil) (subnuminstance nil)) (is (find-objc-class "NSNumber")) (setq class (is (c2mop:ensure-class class-name :direct-superclasses (list (find-objc-class "NSObject")) :direct-slots '((:name foos :type list :initargs (:foos)) (:name foo-count :foreign-type :int)) :metaclass (find-objc-meta-class "NSObject")))) ;; Class initialisation. (is (not (foreign-class-registered-p class))) ;; Method definition. #.(enable-method-syntax) (is (eval `(define-objective-c-generic-function #/prettifyNumber: (self number)))) (is (eval `(define-objective-c-class ,numclass-name (ns::ns-object) ()))) (is (eval `(define-objective-c-method #/prettifyNumber: :int ((self ,numclass-name) (number ns::ns-number)) (- (#/intValue number))))) (is (eval `(define-objective-c-class ,subnumclass-name (,numclass-name) ()))) (is (eval `(define-objective-c-method #/prettifyNumber: :int ((self ,subnumclass-name) (number ns::ns-number)) (* 2 (super))))) ;; We may omit DEFINE-OBJECTIVE-C-GENERIC-FUNCTION. (is (eval `(define-objective-c-method #/foo:bar:pling:do: :int ((x ,class-name) (y :int) z (a t #+(or) (eql +nil+)) (b ns::ns-number) &rest rest) (declare (ignore z rest)) (+ y 20)))) #.(disable-method-syntax) ;; Sanity checks. (is (typep class 'objective-c-class)) (setq instance (is (invoke (invoke class 'alloc) 'init))) (setq numinstance (is (invoke (find-objc-class numclass-name) 'new))) (setq subnuminstance (is (invoke (find-objc-class subnumclass-name) 'new))) ;; Class finalisation. (Should be automatic upon instance ;; creation.) (is (foreign-class-registered-p class)) ;; Method calls. (is (= 170 (invoke instance :foo 150 :bar nil :pling nil :do 100))) (is (= -30 (invoke numinstance :prettify-number 30))) (is (= -60 (invoke subnuminstance :prettify-number 30))) ;; Object identity preservation. (is (eql instance (invoke instance 'self))) ;; FIXME: What's wrong with the following line? It makes CMUCL ;; throw weird errors. #+(or) (is (typep instance class-name)) ;; Slot initialisation. (is (not (slot-boundp instance 'foos))) (is (slot-boundp instance 'foo-count)) ;foreign slots are always SLOT-BOUNDP ;; Slot handling. (setf (slot-value instance 'foos) '(a b c)) (setf (slot-value instance 'foo-count) 3) (is (slot-boundp instance 'foos)) ;; Native slots. (is (objcl-object-backed-by-lisp-class-p instance)) (is (equal (slot-value instance 'foos) (slot-value (invoke instance 'self) 'foos))) (is (typep (slot-value (invoke instance 'self) 'foos) 'list)) (is (equal (slot-value (invoke instance 'self) 'foos) '(a b c))) ;; Foreign slots. (is (equal (slot-value instance 'foo-count) (slot-value (invoke instance 'self) 'foo-count))) (is (typep (slot-value (invoke instance 'self) 'foo-count) 'integer)) (is (equal (slot-value (invoke instance 'self) 'foo-count) 3))))