diff --git a/CHANGELOG b/CHANGELOG index 95e5b58ff8f0ac6c9c298b197cfc4f9e75268d1e..a3edf69308b1ade4ddbf68164f26210e9ca422ed 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,26 @@ +2020-07-24 David Byers <david.byers@liu.se> + + Fix issue #4: + * olc.el (olc-shorten-compound): Better handling of empty response + from nominatim. + (olc-recover-compound): Detect empty response from nominatim. + (olc-position-of): New function. + (olc-encode): Use cl-check-type to detect invalid encoding + lengths. + (general): Make errors more consistent. + (olc-shorten): Correctly signal shortcode and padded errors. + (olc-shorten-compound): Correctly signal shortcode and padded + errors. + (olc-parse-code): Match padding only at pos so parsing continues + if there are zeros later on in the code. The correct codes were + considred invalid, but we got the wrong errors. + + * olc.texi (Errors): Document all the errors. + + * test/olctest.el: Improved error-checking assertion. Added + support for expected failures. + + 2020-07-23 David Byers <david.byers@liu.se> Fix issue #2: diff --git a/Makefile b/Makefile index 8dca7ba2610800359c826c8d17a8e67046a3e168..a12b5b49d89ee3e8383925fb10df5219c191f2e0 100644 --- a/Makefile +++ b/Makefile @@ -48,4 +48,5 @@ test: -l ../olc.el \ -l olctest.el \ -f olctest-batch-test \ + $(TESTS) \ ) diff --git a/olc.el b/olc.el index fa268706af3a2a866b804ef788c713aec3e9d884..85e7cdba4444c0b41d382a16d976f948eec06cf6 100644 --- a/olc.el +++ b/olc.el @@ -3,7 +3,7 @@ ;; Copyright (C) 2020 David Byers ;; ;; Author: David Byers <david.byers@liu.se> -;; Version: 1.0.2 +;; Version: 1.1.0 ;; Package-Requires: ((emacs "25.1")) ;; Keywords: extensions, lisp ;; URL: https://gitlab.liu.se/davby02/olc @@ -56,17 +56,74 @@ ;;; Custom errors: -(define-error 'olc-error "Error in open location code.") +(define-error 'olc-error "Open location code error") + (define-error 'olc-parse-error - "Parse error in open location code" 'olc-error) + "Error parsing open location code" 'olc-error) + +(define-error 'olc-parse-error-unexpected-end + "Unexpected end parsing open location code" + 'olc-parse-error) + +(define-error 'olc-parse-error-invalid-character + "Invalid character parsing open location code" + 'olc-parse-error) + +(define-error 'olc-parse-error-missing-plus + "Missing plus sign parsing open location code" + 'olc-parse-error) + +(define-error 'olc-parse-error-invalid-padding + "Invalid padding parsing open location code" + 'olc-parse-error) + +(define-error 'olc-parse-error-padded-shortcode + "Padded short code parsing open location code" + 'olc-parse-error) + +(define-error 'olc-parse-error-digit-after-padding + "Unexpected digit after padding parsing open location code" + 'olc-parse-error) + +(define-error 'olc-parse-error-empty-code + "Empty code when parsing open location code" + 'olc-parse-error) + (define-error 'olc-decode-error - "Error decoding open location code" 'olc-error) -(define-error 'olc-encode-error - "Error encoding open location code" 'olc-error) + "Error decoding open location code" + 'olc-error) + +(define-error 'olc-decode-error-shortcode + "Short codes must be recovered before decoding" + 'olc-decode-error) + (define-error 'olc-shorten-error - "Error shortening open location code" 'olc-error) + "Error shortening open location code." + 'olc-error) + +(define-error 'olc-shorten-error-shortcode + "Code is already shortened" + 'olc-shorten-error) + +(define-error 'olc-shorten-error-padded + "Unable to shorten padded codes" + 'olc-shorten-error) + (define-error 'olc-recover-error - "Error recovering open location code" 'olc-error) + "Error recovering open location code." + 'olc-error) + +(define-error 'olc-recover-error-reference-search-failed + "Reference location search failed" + 'olc-recover-error) + +(define-error 'olc-recover-error-reference-not-found + "Reference location not found" + 'olc-recover-error) + +(define-error 'olc-recover-error-invalid-reference + "Invalid reference location" + 'olc-recover-error) ;;; Base 20 digits: @@ -137,11 +194,21 @@ SPEC is a list consisting of an error to catch, the error to raise, and args for the raised error. \(fn (CATCH SIGNAL &rest ARGS) BODY...)" - (declare (indent 1)) + (declare (indent 1) (debug (listp &rest form))) `(condition-case nil ,@body (,(elt spec 0) (signal ',(elt spec 1) (list ,@(cddr spec)))))) +(defsubst olc-position-of (char code) + "Find the leftmost position of CHAR in CODE." + (let ((index 0)) + (catch 'result + (mapc (lambda (letter) + (when (= char letter) + (throw 'result index)) + (setq index (1+ index))) + code)))) + (defsubst olc-clip-latitude (lat) "Clip LAT to -90,90." (max -90 (min 90 lat))) @@ -182,49 +249,50 @@ raise, and args for the raised error. (catch 'break (while (< pos (length code)) (olc-transform-error - (args-out-of-range olc-parse-error - "code too short" code (1+ pos)) + (args-out-of-range olc-parse-error-unexpected-end + code (1+ pos)) (cond ((eq (elt code pos) ?+) (throw 'break nil)) ((eq (elt code pos) ?0) (throw 'break nil)) ((= (length pairs) 4) (throw 'break nil)) ((not (olc-valid-char (elt code pos))) - (signal 'olc-parse-error - (list "invalid character" pos code))) + (signal 'olc-parse-error-invalid-character + (list code pos (string (elt code pos))))) ((not (olc-valid-char (elt code (1+ pos)))) - (signal 'olc-parse-error - (list "invalid character" (1+ pos) code))) + (signal 'olc-parse-error-invalid-character + (list code (1+ pos) + (string (elt code (1+ pos)))))) (t (setq pairs (cons (cons (elt code pos) (elt code (1+ pos))) pairs))))) (setq pos (+ pos 2)))) ;; Measure the padding - (when (string-match "0+" code pos) + (when (eq pos (string-match "0+" code pos)) (setq pos (match-end 0) padding (- (match-end 0) (match-beginning 0)))) ;; Parse the separator (olc-transform-error - (args-out-of-range olc-parse-error - "code too short" code pos) + (args-out-of-range olc-parse-error-unexpected-end + code pos) (if (eq (elt code pos) ?+) (setq pos (1+ pos)) - (signal 'olc-parse-error - (list "missing separator" pos code)))) + (signal 'olc-parse-error-missing-plus + (list code pos)))) ;; Check the length of the padding (unless (and (= (% padding 2) 0) (<= (+ padding (* 2 (length pairs))) 8)) - (signal 'olc-parse-error - (list "incorrect padding length" pos code))) + (signal 'olc-parse-error-invalid-padding + (list code pos))) ;; Determine if the code is shortened or not (setq short (< (+ (* 2 (length pairs)) padding) 8)) ;; We cant be short and have padding (not sure why) (when (and short (> padding 0)) - (signal 'olc-parse-error - (list "padded codes can't be shortened" pos code))) + (signal 'olc-parse-error-padded-shortcode + (list code pos))) ;; Determine the precision of the code (setq precision (- 8 padding)) @@ -232,32 +300,38 @@ raise, and args for the raised error. ;; Parse what's after the separator (when (< pos (length code)) (when (> padding 0) - (signal 'olc-parse-error - (list "padding followed by data" pos code))) + (signal 'olc-parse-error-digit-after-padding + (list code pos (string (elt code pos))))) ;; Parse one more pair - (olc-transform-error - (args-out-of-range olc-parse-error - "code too short" code (1+ pos)) - (setq pairs (cons (cons (elt code pos) - (elt code (1+ pos))) - pairs) - pos (+ 2 pos) - precision (+ 2 precision))) + (olc-transform-error (args-out-of-range + olc-parse-error-unexpected-end + code (1+ pos)) + (cond ((not (olc-valid-char (elt code pos))) + (signal 'olc-parse-error-invalid-character + (list code pos (string (elt code pos))))) + ((not (olc-valid-char (elt code (1+ pos)))) + (signal 'olc-parse-error-invalid-character + (list code (1+ pos) (string (elt code (1+ pos)))))) + (t (setq pairs (cons (cons (elt code pos) + (elt code (1+ pos))) + pairs) + pos (+ 2 pos) + precision (+ 2 precision)))))) ;; Parse grid (while (< pos (length code)) (cond ((not (olc-valid-char (elt code pos))) - (signal 'olc-parse-error - (list "invalid character" pos code))) + (signal 'olc-parse-error-invalid-character + (list code pos (string (elt code pos))))) ((>= (length grid) 5) (setq pos (1+ pos))) (t (setq grid (cons (elt code pos) grid) pos (1+ pos) - precision (1+ precision)))))) + precision (1+ precision))))) ;; Check for an empty code (unless pairs - (signal 'olc-parse-error (list "invalid code" 0 code))) + (signal 'olc-parse-error-empty-code (list code 0))) ;; Return the result (olc-parse-create :pairs (nreverse pairs) @@ -339,19 +413,14 @@ invalid." (cl-defun olc-encode (lat lon &key (len 10)) "Encode LAT and LON as a LEN length open location code. -LEN is automatically clipped to between 2 and 15. -`olc-encode-error' is raised if it is otherwise invalid (i.e. 3, -5, 7, or 9). If LEN is not specified, it defaults to 10. - -Returns an olc-area structure. Raises olc-encode-error if the -values cannot (legally) be encoded to the selected length." +LEN is automatically clipped to between 2 and 15. Invalid values +raise an error." (cl-check-type lat number) (cl-check-type lon number) (cl-check-type len integer) (setq len (max 2 (min 15 len))) - (when (and (< len 11) (/= (% len 2) 0)) - (signal 'olc-encode-error "invalid encoding length")) + (cl-check-type len (member 2 4 6 8 10 11 12 13 14 15)) (setq lat (olc-normalize-latitude lat len) lon (olc-normalize-longitude lon)) @@ -413,7 +482,7 @@ differences, however, are extremely small." ;; We only deal with long codes (when (olc-parse-short parse) - (signal 'olc-decode-error code)) + (signal 'olc-decode-error-shortcode (list code))) ;; Process the pairs (mapc (lambda (pair) @@ -454,14 +523,15 @@ shortened code, of if LIMIT is not positive and even." (cl-check-type lon number) (cl-check-type limit (member 2 4 6 8 10 12)) + (when (olc-is-short code) + (signal 'olc-shorten-error-shortcode + (list code))) + (let* ((parse (olc-parse-code code)) (area (olc-decode parse))) - (when (olc-is-short parse) - (signal 'olc-shorten-error - (list "can't shorten shortened codes" code))) (when (< (olc-parse-precision parse) 8) - (signal 'olc-shorten-error - (list "can't shorten padded codes" code))) + (signal 'olc-shorten-error-padded + (list code))) (setq lat (olc-clip-latitude lat) lon (olc-normalize-longitude lon)) @@ -503,64 +573,75 @@ faster. (cl-check-type limit (member 2 4 6 8 10 12)) (cl-check-type zoom (or integer listp)) - (save-match-data - (let* ((area (olc-decode code)) - (zoom-lo (cond ((numberp zoom) zoom) - ((listp zoom) (elt zoom 0)) - (t (signal 'args-out-of-range zoom)))) - (zoom-hi (cond ((numberp zoom) zoom) - ((listp zoom) (elt zoom 1)) - (t (signal 'args-out-of-range zoom)))) - result) - - ;; Check that zoom range is not inverted - (when (or (< zoom-hi zoom-lo) - (< zoom-hi 1) (> zoom-hi 18) - (< zoom-lo 1) (> zoom-lo 18)) - (signal 'args-out-of-range zoom)) - - ;; Otherwise we may never hit the high limit - (setq zoom-hi (1+ zoom-hi)) - - (catch 'result - (while (< zoom-lo zoom-hi) - (let* ((zoom (floor (+ zoom-lo zoom-hi) 2)) - (resp (request-response-data - (request - "https://nominatim.openstreetmap.org/reverse" - :params `((lat . ,(olc-area-lat area)) - (lon . ,(olc-area-lon area)) - (zoom . ,zoom) - (format . "json")) - :parser #'json-read - :sync t))) - (tmp-code - (when resp - (olc-shorten code - (string-to-number - (alist-get 'lat resp)) - (string-to-number - (alist-get 'lon resp)) - :limit limit))) - (padlen (when (string-match "+" tmp-code) - (- 8 (match-beginning 0))))) + (when (olc-is-short code) + (signal 'olc-shorten-error-shortcode + (list code))) + + (let* ((parse (olc-parse-code code)) + (area (olc-decode code)) + (zoom-lo (cond ((numberp zoom) zoom) + ((listp zoom) (elt zoom 0)) + (t (signal 'args-out-of-range (list '(1 18) zoom))))) + (zoom-hi (cond ((numberp zoom) zoom) + ((listp zoom) (elt zoom 1)) + (t (signal 'args-out-of-range (list '(1 18) zoom))))) + result) + + ;; Check for padding + (when (< (olc-parse-precision parse) 8) + (signal 'olc-shorten-error-padded + (list code))) + + ;; Check that zoom range is not inverted + (when (or (< zoom-hi zoom-lo) + (< zoom-hi 1) (> zoom-hi 18) + (< zoom-lo 1) (> zoom-lo 18)) + (signal 'args-out-of-range (list '(1 18) zoom))) + + ;; Otherwise we may never hit the high limit + (setq zoom-hi (1+ zoom-hi)) + + (catch 'result + (while (< zoom-lo zoom-hi) + (let* ((zoom (floor (+ zoom-lo zoom-hi) 2)) + (resp (request-response-data + (request + "https://nominatim.openstreetmap.org/reverse" + :params `((lat . ,(olc-area-lat area)) + (lon . ,(olc-area-lon area)) + (zoom . ,zoom) + (format . "json")) + :parser #'json-read + :sync t))) + (tmp-code + (when resp + (olc-shorten code + (string-to-number (alist-get 'lat resp)) + (string-to-number (alist-get 'lon resp)) + :limit limit))) + (padlen + (when tmp-code (- 8 (olc-position-of ?+ tmp-code))))) + + ;; If resp is nil, then there's no point in going further + + (if (null resp) + (setq zoom-lo zoom-hi) ;; Keep the shortest code we see that has at most limit ;; chars removed + (when (and (<= padlen limit) (or (null result) (< (length tmp-code) (length (car result))))) - (setq result (cons tmp-code - (alist-get 'display_name resp)))) + (setq result (cons tmp-code (alist-get 'display_name resp)))) ;; Zoom in or out (if (< padlen limit) (setq zoom-lo (1+ zoom)) - (setq zoom-hi zoom)))) - (if (and result (> 8 (progn (string-match "+" (car result)) - (match-end 0)))) - (concat (car result) " " (cdr result)) - code))))) + (setq zoom-hi zoom))))) + (if (and result (< (olc-position-of ?+ (car result)) 8)) + (concat (car result) " " (cdr result)) + code)))) (cl-defun olc-recover (code lat lon &key (format 'area)) @@ -622,7 +703,7 @@ If FORMAT is `area' (or any other value), the returned value is an full open location code." ;; Make sure we can do requests (save-match-data - (unless (fboundp 'request) (signal 'void-function 'request)) + (unless (fboundp 'request) (signal 'void-function '(request))) ;; Check types (defer check of ref) (cl-check-type code stringp) @@ -648,9 +729,12 @@ full open location code." ;; Check that we got a response (unless (eq 200 (request-response-status-code resp)) - (signal 'olc-recover-error - (list "error decoding reference" - (request-response-status-code resp)))) + (signal 'olc-recover-error-reference-search-failed + (list code ref))) + + (unless (> (length (request-response-data resp)) 0) + (signal 'olc-recover-error-reference-not-found + (list code ref))) (let* ((data (elt (request-response-data resp) 0)) (lat (alist-get 'lat data)) @@ -658,9 +742,8 @@ full open location code." ;; Check that we have a lat and lon (unless (and lat lon) - (signal 'olc-recover-error - (list "reference location missing lat or lon" - data))) + (signal 'olc-recover-error-invalid-reference + (list code ref))) ;; Finally recover the code! (olc-recover code diff --git a/olc.info b/olc.info index 5a13a41ae3d786c9388264a2e5a275540d56f3b0..fcfb028bcb60a005970e3af96a0f0b38827f0922 100644 --- a/olc.info +++ b/olc.info @@ -22,11 +22,12 @@ languages, see https://github.com/google/open-location-code. * Menu: * Data types:: Data types defined by olc. +* Errors:: Errors raised by olc. * Functions:: Functions defined by olc. * Index:: Type and function index. -File: olc.info, Node: Data types, Next: Functions, Prev: Top, Up: Top +File: olc.info, Node: Data types, Next: Errors, Prev: Top, Up: Top Data types ********** @@ -107,7 +108,101 @@ The olc-parse is a structure mainly used internally. Unless you call shortened, but don’t count on this. -File: olc.info, Node: Functions, Prev: Data types, Up: Top +File: olc.info, Node: Errors, Next: Functions, Prev: Data types, Up: Top + +Errors +====== + +‘olc-error’ + The message is ‘Open location code error’. This is the parent of + all errors in olc. + +‘olc-parse-error’ + The message is ‘Error parsing open location code’. This is the + parent of the various parse errors. All parse errors have the same + associated data: (CODE POS DATA...), where POS is the approximate + position of the parse error, CODE is the code being parsed, and + DATA depends on the error. + +‘olc-parse-error-unexpected-end’ + The message is ‘Unexpected end parsing open location code’. Raised + when the code is incomplete. + +‘olc-parse-error-invalid-character’ + The message is ‘Invalid character parsing open location code’. + Raised when an invalid character is encountered. DATA is a string + containing the invalid character. + +‘olc-parse-error-missing-plus’ + The message is ‘Missing plus sign parsing open location code’. + Raised when the plus sign is missing. + +‘olc-parse-error-invalid-padding’ + The message is ‘Invalid padding parsing open location code’. + Raised when the padding is invalid (e.g. odd in length). + +‘olc-parse-error-padded-shortcode’ + The message is ‘Padded short code parsing open location code’. + Raised when parsing a code with padding that has been shortened. + +‘olc-parse-error-digit-after-padding’ + The message is ‘Unexpected digit after padding parsing open + location code’. Raised when an unexpected digit is encountered + (e.g. after padding). + +‘olc-parse-error-empty-code’ + The message is ‘Empty code when parsing open location code’. + Raised when the code is empty (i.e. ‘+’). + +‘olc-decode-error’ + The message is ‘Error decoding open location code’. The associated + data is a list containing the code being decoded as its first + element. This is the parent of the various decoding errors raised + by ‘olc-decode’. + +‘olc-decode-error-shortcode’ + The message is ‘Short codes must be recovered before decoding’. + This is raised when an attempt is made to decode a shortened code. + +‘olc-shorten-error’ + The message is ‘Error shortening open location code’. The + associated data is a list containing the code being decoded as its + first element. This is the parent of the various shortening errors + raised by ‘olc-shorten’ and ‘olc-shorten-compound’. + +‘olc-shorten-error-shortcode’ + The message is ‘Code is already shortened’. Raised when attempting + to shorten a shortened code. + +‘olc-shorten-error-padding’ + The message is ‘Unable to shorten padded codes’. Raised when + attempting to shorten a code with padding. + +‘olc-recover-error’ + The message is ‘Error recovering open location code’. The + associated data depends on the exact code, but is always a list + with the code being recovered as its first element. This is the + parent of the various recovery errors raised by ‘olc-recover’ and + ‘olc-recover-compound’. + +‘olc-recover-error-geocoding-request’ + The messags is ‘Reference location search failed’. Raised when + geographical search fails due to an error at the HTTP layer. The + associated data is (CODE, REF) where REF is the reference being + searched. + +‘olc-recover-error-geocoding-not-found’ + The messags is ‘Reference location not found’. Raised when + geographical search returns no result. The associated data is + (CODE, REF) where REF is the reference being searched. + +‘olc-recover-error-geocoding-invalid’ + The messags is ‘Invalid reference location’. Raised when + geographical search returns an invalid result. The associated data + is (CODE, REF) where REF is the reference being searched. + + +File: olc.info, Node: Functions, Next: Index, Prev: Errors, Up: Top Functions ========= @@ -264,11 +359,12 @@ Index Tag Table: Node: Top67 -Node: Data types861 -Node: olc-area1242 -Node: olc-parse2170 -Node: Functions3400 -Node: Index8754 +Node: Data types899 +Node: olc-area1277 +Node: olc-parse2205 +Node: Errors3435 +Node: Functions7375 +Node: Index12739 End Tag Table diff --git a/olc.texi b/olc.texi index c86716f99fa35c8a39f2e319bf9e8731c9020cb0..cf5fa147c35dbf8da69aec3c6c5368eed02bb125 100644 --- a/olc.texi +++ b/olc.texi @@ -26,11 +26,12 @@ languages, see https://github.com/google/open-location-code. @menu * Data types:: Data types defined by olc. +* Errors:: Errors raised by olc. * Functions:: Functions defined by olc. * Index:: Type and function index. @end menu -@node Data types,Functions,,Top +@node Data types,Errors,,Top @unnumbered Data types olc defines two data types: olc-area and olc-parse. The former @@ -117,7 +118,105 @@ but don't count on this. @end defun -@node Functions,,Data types,Top +@node Errors,Functions,Data types,Top +@unnumberedsec Errors + +@table @code +@item olc-error +The message is @samp{Open location code error}. This is the parent of +all errors in olc. + +@item olc-parse-error +The message is @samp{Error parsing open location code}. This is the +parent of the various parse errors. All parse errors have the same +associated data: (@var{code} @var{pos} @var{data}...), where @var{pos} +is the approximate position of the parse error, @var{code} is the +code being parsed, and @var{data} depends on the error. + +@item olc-parse-error-unexpected-end +The message is @samp{Unexpected end parsing open location +code}. Raised when the code is incomplete. + +@item olc-parse-error-invalid-character +The message is @samp{Invalid character parsing open location +code}. Raised when an invalid character is encountered. @var{data} is +a string containing the invalid character. + +@item olc-parse-error-missing-plus +The message is @samp{Missing plus sign parsing open location +code}. Raised when the plus sign is missing. + +@item olc-parse-error-invalid-padding +The message is @samp{Invalid padding parsing open location +code}. Raised when the padding is invalid (e.g. odd in length). + +@item olc-parse-error-padded-shortcode +The message is @samp{Padded short code parsing open location +code}. Raised when parsing a code with padding that has been +shortened. + +@item olc-parse-error-digit-after-padding +The message is @samp{Unexpected digit after padding parsing open +location code}. Raised when an unexpected digit is encountered +(e.g. after padding). + +@item olc-parse-error-empty-code +The message is @samp{Empty code when parsing open location +code}. Raised when the code is empty (i.e. @samp{+}). + +@item olc-decode-error +The message is @samp{Error decoding open location code}. The +associated data is a list containing the code being decoded as its +first element. This is the parent of the various decoding errors +raised by @code{olc-decode}. + +@item olc-decode-error-shortcode +The message is @samp{Short codes must be recovered before +decoding}. This is raised when an attempt is made to decode a +shortened code. + +@item olc-shorten-error +The message is @samp{Error shortening open location code}. The +associated data is a list containing the code being decoded as its +first element. This is the parent of the various shortening errors +raised by @code{olc-shorten} and @code{olc-shorten-compound}. + +@item olc-shorten-error-shortcode +The message is @samp{Code is already shortened}. Raised when +attempting to shorten a shortened code. + +@item olc-shorten-error-padding +The message is @samp{Unable to shorten padded codes}. Raised when +attempting to shorten a code with padding. + +@item olc-recover-error +The message is @samp{Error recovering open location code}. The +associated data depends on the exact code, but is always a list with +the code being recovered as its first element. This is the parent of +the various recovery errors raised by @code{olc-recover} and +@code{olc-recover-compound}. + +@item olc-recover-error-geocoding-request +The messags is @samp{Reference location search failed}. Raised when +geographical search fails due to an error at the HTTP layer. The +associated data is (@var{code}, @var{ref}) where @var{ref} is the +reference being searched. + +@item olc-recover-error-geocoding-not-found +The messags is @samp{Reference location not found}. Raised when +geographical search returns no result. The associated data is +(@var{code}, @var{ref}) where @var{ref} is the reference being +searched. + +@item olc-recover-error-geocoding-invalid +The messags is @samp{Invalid reference location}. Raised when +geographical search returns an invalid result. The associated data is +(@var{code}, @var{ref}) where @var{ref} is the reference being +searched. + +@end table + +@node Functions,Index,Errors,Top @unnumberedsec Functions @defun olc-encode lat lon &key length diff --git a/test/olctest.el b/test/olctest.el index 0e0f4dbceeb6aa22fd481fd895b3145ebf319a26..29721e5a78472896f6eaa4c3d7226b2fbfea746e 100644 --- a/test/olctest.el +++ b/test/olctest.el @@ -35,7 +35,7 @@ (--olctest-current-case ,name)) (message "olctest running %s" ,name) ,@body - (olctest-report-results --olctest-results))) + (olctest-report-results (reverse --olctest-results)))) (cl-defun olctest-record-failure (&key exp act msg) @@ -47,6 +47,14 @@ (act . ,act)) --olctest-results))) +(defmacro olctest-expect-failure (name &rest body) + "Expect a failure." + (declare (indent 1) (debug (form &rest form))) + `(unless (let ((--olctest-results nil)) + ,@body + --olctest-results) + (olctest-record-failure :exp 'failure :act 'success :msg ,name))) + (defun olctest-report-results (results) "Report results from tests." (if (null results) @@ -74,7 +82,13 @@ (declare (indent 1)) `(when (condition-case --olctest-caught-error (progn ,@body t) - (,exp nil) + ,@(mapcar (lambda (spec) + (cond ((symbolp spec) `(,spec nil)) + ((listp spec) + `(,(car spec) + (olctest-equal :exp ',spec :act --olctest-caught-error) nil)) + (t (error "invalid olctest error specification")))) + exp) (error (olctest-record-failure :exp ',exp :act --olctest-caught-error :msg ,msg) nil)) (olctest-record-failure :exp ',exp :act 'noerror :msg ,msg))) @@ -244,7 +258,11 @@ (len (alist-get 'len case)) (shortcode (alist-get 'exp case)) (actual (olc-shorten fullcode lat lon :limit len))) - (olctest-string= :exp shortcode :act actual :msg len))))) + (olctest-string= :exp shortcode :act actual :msg len))) + + (olctest-equal :act (olc-shorten-compound "546FWWM2+F6") + :exp "WWM2+F6 Adamstown, Pitcairn") + )) (defun olctest-issue-3 () @@ -413,16 +431,91 @@ )) +(defun olctest-errors () + (olctest-testcase "local:errors" + (olctest-assert-error (:exp ((olc-parse-error-unexpected-end "22" 2)) :msg "P1") + (olc-parse-code "22")) + + (olctest-assert-error (:exp ((olc-parse-error-invalid-character "O2+" 0 "O")) :msg "P2") + (olc-parse-code "O2+")) + + (olctest-assert-error (:exp ((olc-parse-error-invalid-character "2O+" 1 "O")) :msg "P3") + (olc-parse-code "2O+")) + + (olctest-assert-error (:exp ((olc-parse-error-invalid-character "20+" 1 "0")) :msg "P4") + (olc-parse-code "20+")) + + (olctest-assert-error (:exp ((olc-parse-error-unexpected-end "FFFFFFFF" 8)) :msg "P5") + (olc-parse-code "FFFFFFFF")) + + (olctest-assert-error (:exp ((olc-parse-error-missing-plus "FFFFFFFFF" 8)) :msg "P6") + (olc-parse-code "FFFFFFFFF")) + + (olctest-assert-error (:exp ((olc-parse-error-padded-shortcode "FF0000+" 7)) :msg "P7") + (olc-parse-code "FF0000+")) + + (olctest-assert-error (:exp ((olc-parse-error-invalid-padding "FF00000+" 8)) :msg "P8") + (olc-parse-code "FF00000+")) + + (olctest-assert-error (:exp ((olc-parse-error-digit-after-padding "FF000000+FF" 9 "F")) :msg "P9") + (olc-parse-code "FF000000+FF")) + + (olctest-assert-error (:exp ((olc-parse-error-unexpected-end "FFFFFFFF+F" 10)) :msg "P10") + (olc-parse-code "FFFFFFFF+F")) + + (olctest-assert-error (:exp ((olc-parse-error-invalid-character "FFFFFFFF+F0" 10 "0")) :msg "P11") + (olc-parse-code "FFFFFFFF+F0")) + + (olctest-assert-error (:exp ((olc-parse-error-invalid-character "FFFFFFFF+FF0" 11 "0")) :msg "P12") + (olc-parse-code "FFFFFFFF+FF0")) + + (olctest-assert-error (:exp ((olc-parse-error-empty-code "+" 0)) :msg "P13") + (olc-parse-code "+")) + + (olctest-assert-error (:exp ((olc-decode-error-shortcode "22+")) :msg "D1") + (olc-decode "22+")) + + (olctest-assert-error (:exp ((olc-shorten-error-padded "22222200+")) :msg "S1") + (olc-shorten "22222200+" 0 0)) + + (olctest-assert-error (:exp ((olc-shorten-error-shortcode "22+")) :msg "S2") + (olc-shorten-compound "22+")) + + (olctest-assert-error (:exp ((olc-shorten-error-padded "FFFFFF00+")) :msg "S3") + (olc-shorten-compound "FFFFFF00+")) + + (olctest-assert-error (:exp ((olc-recover-error-reference-not-found + "22+" "Nowhere Special, Pitcairn")) :msg "R1") + (olc-recover-compound "22+ Nowhere Special, Pitcairn")) + + (olctest-expect-failure "R2" + (let ((olc-nominatim-url "https://invalid.domain/nominatim")) + (olctest-assert-error (:exp ((olc-recover-error-reference-search-failed + "22+" "Sweden")) :msg "R2") + (olc-recover-compound "22+ Sweden")))) + + )) + + +(defmacro run-test (arg) + `(or (null (ignore-errors olctest-selected-tests)) + (not (memq ',arg olctest-selected-tests)) + (funcall (intern (concat "olctest-" (symbol-name ',arg)))))) + (defun olctest-run-all () "Run all tests." - (and (olctest-decode) - (olctest-encode) - (olctest-shortcodes) - (olctest-validity) - (olctest-localtests) - (olctest-issue-3) - (olctest-issue-2) - (olctest-issue-1) + (let ((olctest-selected-tests + (mapcar 'intern command-line-args-left))) + (and (run-test decode) + (run-test encode) + (run-test shortcodes) + (run-test validity) + (run-test localtests) + (run-test errors) + (run-test issue-3) + (run-test issue-2) + (run-test issue-1) + ) )) (defun olctest-batch-test () @@ -431,3 +524,4 @@ (olctest-run-all) (error (message (format "error: %s %s" (car err) (cdr err))) nil)) 0 1))) +