diff --git a/src/tests/2am.lisp b/src/tests/2am.lisp index ffcd41abf..5d08bfe67 100644 --- a/src/tests/2am.lisp +++ b/src/tests/2am.lisp @@ -206,12 +206,17 @@ "Assert that `body' signals a condition of type `condition'." `(%signals ',condition (lambda () ,form) ,@args)) -(defmacro finishes (form) - `(handler-case (progn - ,form - (passed)) - (serious-condition (c) - (failed (make-condition 'test-failure - :name *test-name* - :format-control "Expected to finish, but got ~s" - :format-arguments (list (type-of c))))))) +(defmacro finishes (form &rest args) + (if args + `(handler-case (progn ,form (passed)) + (serious-condition (c) + (failed (make-condition 'test-failure + :name *test-name* + :format-control ,(car args) + :format-arguments (list ,@(cdr args)))))) + `(handler-case (progn ,form (passed)) + (serious-condition (c) + (failed (make-condition 'test-failure + :name *test-name* + :format-control "Expected to finish, but got ~s" + :format-arguments (list (type-of c)))))))) diff --git a/src/tests/ecl-tests.asd b/src/tests/ecl-tests.asd index 20aedd6b1..fa5ec7638 100644 --- a/src/tests/ecl-tests.asd +++ b/src/tests/ecl-tests.asd @@ -24,7 +24,8 @@ (:file "ieee-fp" :if-feature :ieee-floating-point) (:file "package-extensions") (:file "hash-tables") - (:file "external-formats" :if-feature :unicode))) + (:file "external-formats" :if-feature :unicode) + (:file "complex"))) (:module stress-tests :default-component-class asdf:cl-source-file.lsp :components diff --git a/src/tests/ecl-tests.lisp b/src/tests/ecl-tests.lisp index e3ca7f473..e18755b2e 100644 --- a/src/tests/ecl-tests.lisp +++ b/src/tests/ecl-tests.lisp @@ -22,7 +22,7 @@ ;;;; Declare the suites (suite 'ecl-tests '(executable ieee-fp eprocess package-ext hash-tables ansi+ mixed - cmp emb ffi mop mp run-program eformat)) + cmp emb ffi mop mp run-program eformat complex)) (suite 'make-check '(executable ieee-fp eprocess package-ext hash-tables ansi+ mixed diff --git a/src/tests/normal-tests/complex.lsp b/src/tests/normal-tests/complex.lsp new file mode 100644 index 000000000..95851387a --- /dev/null +++ b/src/tests/normal-tests/complex.lsp @@ -0,0 +1,291 @@ + +(in-package :cl-test) +(suite 'complex) + +;;; Test suite is for testing complex types (added as part of adding +;;; complex float as a native type). + +(test complex.0001.upgradex-complex-part-type + (signals error (upgraded-complex-part-type 'string)) + (mapc (lambda (upgraded-typespec typespec) + (is (eql upgraded-typespec (upgraded-complex-part-type typespec)) + "upgraded-complex-part-type of ~s should be ~s, is ~s" + typespec upgraded-typespec (upgraded-complex-part-type typespec))) + #-complex-float (make-list 10 :initial-element 'real) + #+complex-float (append (make-list 5 :initial-element 'rational) + '(float single-float single-float double-float long-float)) + '(rational integer fixnum (integer 0 1) ratio + float short-float single-float double-float long-float))) + +(test complex.0002.type-of + (mapc (lambda (type element) + (is (equal type (type-of element)) "(type-of ~s) not equal to ~s, is ~s" + element type (type-of element))) + #-complex-float (make-list 6 :initial-element '(complex real)) + #+complex-float '((complex rational) + (complex rational) + (complex single-float) + (complex single-float) + (complex double-float) + (complex long-float)) + (list #c(1 2) + #c(1/2 3/2) + #c(1.2 3.2) + #c(1.2s0 3.2s0) + #c(1.2d0 3.2d0) + #c(1.2l0 3.2l0)))) + +;;; ensures that common arithmetic operations doesn't fail +(defmacro without-fpe-traps (&body body) + `(let ((bits (si:trap-fpe 'cl:last t))) + (unwind-protect + (progn (si:trap-fpe t nil) + ,@body) + (si:trap-fpe bits t)))) + +(defmacro should-signal (type &body body) + `(handler-case (progn ,@body) + (:no-error (&rest args) + (finishes (error "should signal ~s" ',type))) + (,type () t))) + +(test complex.0003.arith + (without-fpe-traps + (flet ((check-op-1 (op) + "Check if 1-argument operation succeeds." + (mapc op *complexes*)) + (check-op-2 (op) + "Check if pair-wise operation succeeds." + (mapc (lambda (elt) + (mapc (lambda (set) + (dolist (v set) + (funcall op elt v) + (funcall op v elt))) + (list *integers* *floats* *ratios* *complexes*))) + *complexes*))) + (finishes + (mapc (lambda (op) (finishes (check-op-1 op))) + (list #'conjugate #'realpart #'imagpart ; complexes + #'atan #'sin #'sinh #'tan #'tanh #'cos #'cosh ; trigonometry + #'- #'+ #'* #'/ #'1- #'1+ ; basic artih + #'abs #'exp #'log #'sqrt ; exponentials + #'= #'/= #'zerop ; predicates + )) + "complex valid operations crashed") + ;; operations without meaning for complex + (finishes + (mapc (lambda (op) (should-signal type-error (check-op-1 op))) + (list #'ceiling #'floor #'minusp #'plusp #'round #'truncate + #'< #'<= #'> #'>= #'min #'max))) + ;; trigonometry (atan2 corner cases) + (check-op-1 (lambda (elt) (should-signal error (atan elt 42)))) + (check-op-1 (lambda (elt) (should-signal error (atan elt #c(1 1))))) + (check-op-1 (lambda (elt) (should-signal error (atan elt #c(1.0 1.0))))) + (finishes (check-op-2 #'+)) + (finishes (check-op-2 #'-)) + (finishes (check-op-2 #'*)) + (finishes (check-op-2 (lambda (elt1 elt2) + (unless (zerop elt2) + (/ elt1 elt2))))) + ;; See https://gitlab.com/embeddable-common-lisp/ecl/issues/485 + (finishes (check-op-2 (lambda (elt1 elt2) + (when (and (< (realpart elt2) 4096) + (> (realpart elt2) -4095)) + (unless (and (zerop elt1) + (not (zerop elt2)) + (not (plusp (realpart elt2)))) + (expt elt1 elt2)))))) + ;; comparison + (mapc (lambda (op) + (check-op-2 (lambda (elt1 elt2) + (should-signal type-error (funcall op elt1 elt2))))) + (list #'max #'min #'< #'<= #'> #'>= #'>)) + (finishes (check-op-2 #'=) "=<2> complex operation crashed") + (finishes (check-op-2 #'/=) "/=<2> complex operation crashed")))) + +(test complex.0004.contagion + (mapc (lambda (num type) + (is (typep num type) "~s is not typep to ~s (type-of is ~s)" num type (type-of num))) + (list #c(1/4 0) #c(1/4 1) #c(1 0.0) #c(1.0 0) + #c(0.0s0 0.0s0) #c(0.0d0 1) #c(0.0 0.0d0) #c(0.0l0 0.0d0) + (+ 1.0 #c(1 2)) (+ 1.0 #c(1 2.0d0)) (+ 1.0 #c(1 0))) + (list 'rational '(complex rational) '(complex single-float) '(complex single-float) + '(complex single-float) '(complex double-float) '(complex double-float) + '(complex long-float) '(complex single-float) '(complex double-float) 'single-float)) ) + +(test complex.0005.conjugate ; also tests imagpart / realpart + (mapcar (lambda (c1 c2) + (and (= (realpart c1) (realpart c2)) + (= (imagpart c1) (- (imagpart c2))))) + *complexes* + (mapcar #'conjugate *complexes*))) + +(test complex.0006.predicates + (mapcar (lambda (c) + (is (numberp c)) + (is (complexp c)) + (is (not (realp c)))) + *complexes*)) + +;;; These tests are made explicitly for complex-float numbers. +#+complex-float +(let* ((ss1 0.0s0) (ss2 -0.0s0) + (sf1 1.1) (sf2 -42.3) + (sd1 0.0d0) (sd2 +0.2d0) + (sl1 -2.4l0) (sl2 -3.2l0) + (cf0 (si:complex-float ss1 ss2)) + (cf1 (si:complex-float sf1 sf2)) + (cf2 (si:complex-float sd1 sd2)) + (cf3 (si:complex-float sl1 sl2)) + (all-cfloats (list cf0 cf1 cf2 cf3)) + (rf0 #c(1 1))) + + (test cfloat.0000.type + (is (every (lambda (elt) (typep elt 'number)) all-cfloats)) + (is (every (lambda (elt) (typep elt 'complex)) all-cfloats)) + (is (not (some (lambda (elt) (typep elt 'float)) all-cfloats))) + (is (every (lambda (elt) (complexp elt)) all-cfloats)) + (is (not (some (lambda (elt) (realp elt)) all-cfloats))) + (is (not (some (lambda (elt) (floatp elt)) all-cfloats))) + (is (not (some (lambda (elt) (rationalp elt)) all-cfloats))) + (is (subtypep 'si:complex-single-float 'si:complex-float)) + (is (subtypep 'si:complex-double-float 'si:complex-float)) + (is (subtypep 'si:complex-long-float 'si:complex-float)) + (is (subtypep 'si:complex-float 'complex)) + (is (subtypep 'si:complex-float 'number)) + (is (not (subtypep 'si:complex-float 'float))) + (is (not (subtypep 'si:complex-float 'rational))) + (is (not (typep cf1 'si:complex-double-float))) + (is (not (typep cf1 'si:complex-long-float))) + (is (not (typep cf2 'si:complex-single-float))) + (is (not (typep cf2 'si:complex-long-float))) + (is (not (typep cf3 'si:complex-single-float))) + (is (not (typep cf3 'si:complex-double-float)))) + + (test cfloat.0001.predicate + (is (every #'si:complex-float-p all-cfloats)) + (is (not (some #'si:complex-float-p (list nil #\c 3 sf1 rf0))))) + + ;; all numerical operators which doesn't apply to complex + (test cfloat.0002.invalid-type + ;; unary operators + (let ((operations (list #'plusp #'minusp #'max #'min #'evenp #'oddp + #'> #'>= #'< #'<= #'lcm #'isqrt #'cis #'complex + #'numerator #'denominator #'rationalize #'float))) + (mapc (lambda (op) + (mapc (lambda (num) + (signals type-error (funcall op num) "~a" op)) + all-cfloats)) + operations)) + ;; nary operators (implement me!) + ) + + (test cfloat.0003.zerop + (mapc (lambda (num) + (if (and (zerop (realpart num)) + (zerop (imagpart num))) + (is (zerop num)) + (is (not (zerop num))))) + (list* (si:complex-float 0.0s0 0.0s0) + (si:complex-float 0.0d0 0.0d0) + (si:complex-float 0.0l0 0.0l0) + all-cfloats))) + + (test cfloat.0004.realpart/imagpart + (mapc (lambda (num parts) + (is (= (car parts) (realpart num))) + (is (= (cdr parts) (imagpart num)))) + all-cfloats + (list (cons ss1 ss2) + (cons sf1 sf2) + (cons sd1 sd2) + ;; TDD is great! I did put "long" in instead of "long double". + (cons sl1 sl2)))) + + (test cfloat.0005.conjugate + (mapc (lambda (num parts) + (is (= (car parts) (realpart num))) + (is (= (- (cdr parts)) (imagpart num)))) + (mapcar #'conjugate all-cfloats) + (list (cons ss1 ss2) + (cons sf1 sf2) + (cons sd1 sd2) + ;; TDD is great! I did put "long" in instead of "long double". + (cons sl1 sl2)))) + + (test cfloat.0006.abs + (is (every (lambda (num) (typep (abs num) 'real)) all-cfloats)) + (mapc (lambda (elt) + (let ((imag (coerce 0 (type-of elt)))) + (is (= (abs elt) (abs (si:complex-float elt imag)))))) + (list -4.1234s0 +1.000103 +0.1243d0 -2.1234l0))) + + (test cfloat.0007.exponentiation + ;; exp, sqrt, log and log1p + (finishes (mapc (lambda (cf) (exp cf)) all-cfloats) "exp1") + (finishes (mapc (lambda (cf) (sqrt cf)) all-cfloats) "sqrt") + (finishes (mapc (lambda (cf) (if (zerop cf) + (signals division-by-zero (log cf)) + (log cf))) + all-cfloats) "log1") + (finishes (mapc (lambda (cf) (si:log1p cf)) all-cfloats) "log1p") + ;; log operations on floats should give corresponding cfloat type + (mapc (lambda (num) + ;; we assume all only negative or complex are passed here + (let ((result-type (typecase num + (si:complex-single-float 'si:complex-single-float) + (si:complex-double-float 'si:complex-double-float) + (si:complex-long-float 'si:complex-long-float) + (short-float 'si:complex-single-float) + (single-float 'si:complex-single-float) + (double-float 'si:complex-double-float) + (long-float 'si:complex-long-float) + (otherwise 'si:complex-single-float)))) + (is (typep (log num) result-type) "log(~s) is not of type ~s" num result-type) + (is (typep (si:log1p num) result-type) "log(~s) is not of type ~s" num result-type))) + (list -1.12s0 -1.12 -1.12d0 -1.12l0 -4 #c(1/3 3) + (1- most-negative-fixnum) cf1 cf2 cf3))) + + (test cfloat.0008.trig/hyper.1 ; only 1-argument variants + ;; trigonometric + (finishes (mapc (lambda (cf) (sin cf)) all-cfloats) "sin") + (finishes (mapc (lambda (cf) (cos cf)) all-cfloats) "cos") + (finishes (mapc (lambda (cf) (tan cf)) all-cfloats) "tan") + ;; trigonometric arcus + (finishes (mapc (lambda (cf) (asin cf)) all-cfloats) "asin") + (finishes (mapc (lambda (cf) (acos cf)) all-cfloats) "acos") + (finishes (mapc (lambda (cf) (atan cf)) all-cfloats) "atan") + ;; hyperbolic + (finishes (mapc (lambda (cf) (sinh cf)) all-cfloats) "sinh") + (finishes (mapc (lambda (cf) (cosh cf)) all-cfloats) "cosh") + (finishes (mapc (lambda (cf) (tanh cf)) all-cfloats) "tanh") + ;; hyperbolic arcus + (finishes (mapc (lambda (cf) (asinh cf)) all-cfloats) "asinh") + (finishes (mapc (lambda (cf) (acosh cf)) all-cfloats) "acosh") + (finishes (mapc (lambda (cf) (atanh cf)) all-cfloats) "atonh")) + + (test cfloat.0008.make-complex + (signals type-error (complex #c(1.0 1.0) 14)) + (signals type-error (complex 14 #c(1.0 1.0))) + (signals type-error (complex 14 "foobar")) + (is (typep (complex 1 0) 'fixnum)) + (is (typep (complex 1 1) '(complex rational))) + (is (typep (complex 0 1) '(complex rational))) + (is (typep (complex (1+ most-positive-fixnum) 1/4) '(complex rational))) + (is (typep (complex 1/4 (1+ most-positive-fixnum)) '(complex rational))) + (is (typep (complex 1.4 (1+ most-positive-fixnum)) '(complex single-float))) + (is (typep (complex (1+ most-positive-fixnum) 1.4) '(complex single-float))) + (is (typep (complex 1.4d0 1) '(complex double-float))) + (is (typep (complex 1.4d0 1.0l0) '(complex long-float))) + (is (typep (complex 1.4 1.0d0) '(complex double-float)))) + + (test cfloat.0009.c-sharp-reader + ;; reader conses complex-floats (instead of a generic type) + (is (typep #c(1.0s0 2.3) 'si:complex-single-float)) + (is (typep #c(1.0d0 2.3s0) 'si:complex-double-float)) + (is (typep #c(1.0d0 2.3l0) 'si:complex-long-float)) + (is (typep #c(1 2.3l0) 'si:complex-long-float)) + (is (typep #c(1.0 0) 'si:complex-float)) + (is (not (typep #c(1 1) 'si:complex-float))) + (is (not (typep #c(0 1) 'si:complex-float))) + (is (not (typep #c(1 0) 'si:complex-float))))) diff --git a/src/tests/universe.lisp b/src/tests/universe.lisp index 354aa2d89..112d81e5e 100644 --- a/src/tests/universe.lisp +++ b/src/tests/universe.lisp @@ -157,7 +157,7 @@ #C(1.0l0 6.0f0) #C(1.0l0 7.0d0) #C(1/2 1/3) - )) + #C(42 12))) (defparameter *numbers* (append *integers*