├── .gitignore ├── README.md ├── UserScenarios.md ├── corefns-conditions.txt ├── exceptions ├── 4clojure-prob134-AssertionError.ser ├── 4clojure-prob15-ClassCast.ser ├── 4clojure-prob156-AssertionError.ser ├── 4clojure-prob16-Arity.ser ├── 4clojure-prob17-NullPointer.ser ├── 4clojure-prob18-AssertionError.ser ├── 4clojure-prob20-AssertionError.ser ├── 4clojure-prob21-ArityException.ser ├── 4clojure-prob23-IndexOutOfBounds.ser ├── 4clojure-prob24-pre-plus-rewrite-ClassCast.ser ├── 4clojure-prob27-ArityException.ser ├── 4clojure-prob38-ArityException.ser ├── 4clojure-prob57-ArityException.ser ├── 4clojure-prob64-NullPointer.ser ├── ArityException-1-1.ser ├── ClassCast-1-1.ser ├── ClassCast-1-2.ser ├── DrRacket-Exercise2-ClassCast.ser ├── DrRacket-Exercise3-IndexOutOfBounds.ser ├── DrRacket-Exercise9-ArityException.ser ├── IllegalArgument-1-1.ser ├── IllegalArgument-1-2.ser ├── IndexOutOfBounds-1-1.ser ├── NullPointer-1-1.ser ├── add-five-IllegalArgException.ser ├── classcast1.ser ├── compilation_errors │ ├── #_must_be_followed_by_symbol.clj │ ├── eof.clj │ ├── first_argument_must_be_symbol.clj │ ├── greater_than_20_parameters.clj │ ├── greater_than_20_parameters2.clj │ ├── invalid_number.clj │ ├── invalid_token_error1.clj │ ├── invalid_token_error2.clj │ ├── keyword_wrong_number_of_args.clj │ ├── keyword_wrong_number_of_args2.clj │ ├── let-no-matching-pair.clj │ ├── let-odd-number-bindings.clj │ ├── loopevenforms.clj │ ├── non_closing_string.clj │ ├── only_recur_from_tail.clj │ ├── parameter_declaration.clj │ ├── percent_followed_by_letter.clj │ ├── unable_to_resolve_squareThis.clj │ ├── unable_to_resolve_symbol_hello.clj │ ├── unable_to_resolve_world.clj │ ├── unmatched_delim_re.clj │ └── unmatched_delimiter.clj ├── end_of_file.ser ├── generate1.clj └── unmatched_delimiter.ser ├── installation_instructions.md ├── project.clj ├── src ├── corefns │ ├── assert_handling.clj │ ├── collection_fns.clj │ ├── corefns.clj │ ├── failed_asserts_info.clj │ └── specs.clj ├── errors │ ├── dictionaries.clj │ ├── error_dictionary.clj │ ├── error_hints.clj │ ├── errorgui.clj │ ├── exceptionobj.clj │ ├── messageobj.clj │ ├── prettify_exception.clj │ └── stacktraces.clj ├── intro │ ├── core.clj │ └── student.clj └── utilities │ └── defn_generator.clj └── test ├── core_fns_behavior └── core_fns_behavior.clj ├── corefns └── corefns_test.clj ├── errors ├── dictionaries_test.clj ├── error_dictionary_test.clj ├── error_hints_test.clj ├── prettify_exception_test.clj └── testing_tools.clj ├── exception_msgs └── overwritten_fns │ ├── higher_order_fns.clj │ ├── num_fns.clj │ ├── seq_ops.clj │ └── str_fns.clj ├── intro └── student_test.clj ├── location ├── compilation_errors.clj ├── overwritten_fns.clj └── runtime_errors.clj ├── spec_behavior └── spec_test.clj ├── stacktrace_testing └── stacktrace_testing.clj └── utilities ├── file_IO.clj ├── stacktrace_functions.clj └── stacktrace_functions_test.clj /.gitignore: -------------------------------------------------------------------------------- 1 | pom.xml 2 | *jar 3 | /lib/ 4 | /classes/ 5 | /target/classes/* 6 | /target/stale/* 7 | .lein-deps-sum 8 | *.*~ 9 | .*.*.swp 10 | *.nrepl-port 11 | target/ 12 | .lein-plugins 13 | .gitignore.swp 14 | *.silly 15 | .lein-repl-history 16 | /src/intro/test.clj 17 | /src/intro/test_standard.clj 18 | 19 | ### Latex ### 20 | *.aux 21 | *.log 22 | *.out 23 | *.pdf 24 | *synctex.gz 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | clojure-intro-class 2 | =================== 3 | 4 | A pilot project to use Clojure for introductory computer science courses at the University of Minnesota - Morris 5 | 6 | This repository contains both error-related tools, versions of core functions with preconditions, and a string library for beginners. 7 | -------------------------------------------------------------------------------- /UserScenarios.md: -------------------------------------------------------------------------------- 1 | ###User Scenarios 2 | ####Day 1 3 | Suzie starts her first “Hello World” program on the first day of class in a repl from our project. Here are all her code attempts: 4 | * ``(print Hello World)`` 5 | * Clojure message: 6 | * ``CompilerException java.lang.RuntimeException: Unable to resolve symbol: Hello in this context`` 7 | * Our project message: 8 | * ``ERROR: Compilation error: name Hello is undefined, while compiling`` 9 | * Hint/Explanation: 10 | * This breaks because Clojure thinks that Hello is a symbol (identifier) when Suzie wanted it to be just plain text. Our program could respond with a suggestion that if the user wanted the phrase to be plain text to surround it with double quotes. It could also suggest to double-check that the user spelled the symbol correctly. 11 | 12 | * ``(print 'Hello World')`` 13 | * Clojure message: 14 | * ``CompilerException java.lang.RuntimeException: Unable to resolve symbol: World' in this context `` 15 | * Our project message: 16 | * ``ERROR: Compilation error: name World' is undefined, while compiling`` 17 | * Hint/Explanation: 18 | * Be careful about your usage of single quotation marks, they do not behave the same as double quotes. Make sure your strings are wrapped in double quotes. 19 | 20 | * `` print("Hello World")`` 21 | * Clojure message: 22 | * ``ClassCastException java.lang.String cannot be cast to clojure.lang.IFn`` 23 | * Our project message: 24 | * ``ERROR: Attempted to use a string, but a function was expected.`` 25 | * Hint/Explanation: 26 | * This breaks because Clojure thinks the first thing after an open paren should be a function. Our program could respond with a suggestion to the user to make sure that the item right after the open paren is a function, otherwise check syntax. 27 | 28 | * ``(print "Hello World")`` 29 | * Hint/Explanation: 30 | * This works! 31 | 32 | 33 | ####Day 2 34 | For the second day of Clojure, Jaden wants to define a function in a repl from within our project. Here are all his code attempts: 35 | * ``(fn squareThis input*input)`` 36 | * Clojure message: 37 | * ``IllegalArgumentException Parameter declaration input*input should be a vector`` 38 | * Our project message: 39 | * ``ERROR: Parameters in defn should be a vector, but is input*input`` NOTE: WE ACTUALLY USED FN 40 | * Hint/Explanation: 41 | * In this particular case, fn was expecting a vector of parameters and it didn't find one. In the general case, something was looking for a collection and didn't find one. 42 | 43 | * ``(fn squareThis [x] (* x x))``
44 | ``(squareThis 5)`` 45 | * Clojure message: 46 | * ``CompilerException java.lang.RuntimeException: Unable to resolve symbol: squareThis in this context`` 47 | * Our project message: 48 | * ``ERROR: Compilation error: name squareThis is undefined, while compiling`` 49 | * Hint/Explanation: 50 | * If you want squareThis to be a function that you can reference later, use defn instead of fn. 51 | 52 | * ``(defn squareThis [x] (* x x))``
53 | ``(squareThis 5)`` 54 | * Hint/Explanation: 55 | * This works! 56 | 57 | ####Day 3 58 | On the third day of Clojure, Laken is super-excited to start working with lists! She has a list of her favorite bands and she wants to add a new one. Here are her attempts to do this in Clojure: 59 | 60 | * ``(conj "ACDC" ("Daft Punk" "U2" "ZZ Top"))`` 61 | * Clojure message: 62 | * ``ClassCastException java.lang.String cannot be cast to clojure.lang.IFn`` 63 | * Our project message: 64 | * ``ERROR: Attempted to use a string, but a function was expected.`` 65 | * Hint/Explanation: 66 | * Be careful about what comes immediately after an open paren. Clojure might think that it is a function name. 67 | Example: ("add" 5 3) (first argument would be dynamic depending on what type the exception complains about 68 | 69 | * ``(conj "ACDC" '("Daft Punk" "U2" "ZZ Top"))`` 70 | * Clojure message: 71 | * ``ClassCastException java.lang.String cannot be cast to clojure.lang.IPersistentCollection`` 72 | * Our project message: 73 | * ``ERROR: Attempted to use a string, but a collection was expected.`` 74 | * Hint/Explanation: 75 | * Check that the order of arguments for a function are in the correct order. 76 | 77 | * ``(conj '("Daft Punk" "U2" "ZZ Top") "ACDC")`` 78 | * Hint/Explanation: 79 | * This works! 80 | 81 | ####Day 4 82 | On the fourth day of Clojure, Keylan is a little bit nervous to work with Maps. He wants to update a value associated with a key in a map he has created. Here are his attempts: 83 | 84 | * ``(assoc :a 3 {:a 5, :b 8, :c 9})`` 85 | * Clojure message: 86 | * ``ClassCastException clojure.lang.Keyword cannot be cast to clojure.lang.Associative`` 87 | * Our project message: 88 | * ``ERROR: Attempted to use a keyword, but a map or a vector was expected.`` 89 | * Hint/Explanation: 90 | * Check that the order of arguments for a function are in the correct order. 91 | 92 | * ``(assoc (:a 3) {:a 5, :b 8, :c 9})`` 93 | * Clojure message: 94 | * ``ArityException Wrong number of args (2) passed to: core/assoc`` 95 | * Our project message: 96 | * ``ERROR: Wrong number of arguments (2) passed to a function assoc`` 97 | * Hint/Explanation: 98 | * Check the documentation of the function to see how many arguments are required. 99 | (possibly adding links to the documentation to the error message) 100 | 101 | * ``(assoc {:a 3} {:a 5, :b 8, :c 9})`` 102 | * Clojure message: 103 | * ``ArityException Wrong number of args (2) passed to: core/assoc`` 104 | * Our project message: 105 | * ``ERROR: Wrong number of arguments (2) passed to a function assoc`` 106 | * Hint/Explanation: 107 | * Check the documentation of the function to see how many arguments are required. 108 | 109 | * ``(assoc {:a 5, :b 8, :c 9} :a 3)`` 110 | * Hint/Explanation: 111 | * This works! 112 | 113 | ####Day 5 114 | On the fifth day of Clojure, Addison needs to write a function which takes a sequence and returns the maximum value from the sequence without using max or key-max. Here are Addison's attempts: 115 | 116 | * ``(defn [coll] penultimate (last (drop-last coll)))`` 117 | * Clojure message: 118 | * ``IllegalArgumentException First argument to defn must be a symbol`` 119 | * Our project message: 120 | * ``ERROR: First argument to defn must be a symbol`` 121 | * Hint/Explanation: 122 | * 123 | 124 | * ``(defn penultimate [coll] (last (drop-last coll)))``
125 | ``(penultimate [3 4 5 8 9 0 3])`` 126 | * Hint/Explanation: 127 | * This works! 128 | 129 | ####Day 6 130 | On the sixth day of Clojure, Darwin has been given the task to write a print statement that prints out a list of numbers using loop and he needs some help. Here are Darwin's attempts: 131 | 132 | * ``(loop [x 10] (while (> x 1) (println x) (recur (- x 1))))`` 133 | * Clojure message: 134 | * ``CompilerException java.lang.UnsupportedOperationException: Can only recur from tail position`` 135 | * Our project message: 136 | * ``ERROR: Compilation error: recur can only occur as a tail call, meaning no operations can be done after its return, while compiling`` 137 | * Hint/Explanation: 138 | * 139 | 140 | * ``(loop [x 10] (if (= x 0) x (do (println x) (recur (- x 1)))))`` 141 | * Clojure message: 142 | * No error message given 143 | * Our project message: 144 | * ``ERROR: An index in a sequence is out of bounds. The index is: 10`` 145 | * Hint/Explanation 146 | * This works????? (I think??) 147 | 148 | ####Day 7 149 | On the seventh day of Clojure, Geoff has been given the task to write a factorial function that uses recurion and he needs help. Here are his attempts: 150 | 151 | * ``(defn factorial [input] (* input (factorial (- input 1))))``
152 | ``(factorial 5)`` 153 | * Clojure message: 154 | * ``StackOverflowError clojure.lang.Numbers$LongOps.negate (Numbers.java:518)`` 155 | * Our project message: 156 | * ``Exception in thread "main" java.lang.ClassCastException: java.lang.String cannot be cast to java.util.regex.Pattern`` 157 | * Hint/Explanation: 158 | * You may have forgetten a base case for your recursive/looping function. 159 | 160 | * ``(defn factorial [input] (if (>= input 1) (* input (factorial (- input 1))) 1))``
161 | ``(factorial 5)``
162 | ``-> 120`` 163 | * Hint/Explanation 164 | * This works! 165 | 166 | ####Day 8 167 | On the eighth day of Clojure, Symmetra has been given the task to find the square root of the first odd number in a collection. Here are her attempts: 168 | 169 | * ``(defn first-odd-sqrt [my-collection] (Math/sqrt (first (filter odd? my-collection))))``
170 | ``(first-odd-sqrt [2, 4])`` 171 | * Clojure message: 172 | * ``NullPointerException clojure.lang.RT.doubleCast (RT.java:1222)`` 173 | * Our project message: 174 | * ``ERROR: An attempt to access a non-existing object. (NullPointerException)`` 175 | * Hint/Explanation: 176 | * One of the functions you are using might be returning nil for some reason. Try to evaluate the function calls from the inside out to see where this might be occuring. 177 | 178 | * ``(defn first-odd-sqrt [my-collection] (Math/sqrt (filter odd? my-collection)))``
179 | ``(first-odd-sqrt [2, 3, 9, 12, 5])`` 180 | * Clojure message: 181 | * ``ClassCastException clojure.lang.LazySeq cannot be cast to java.lang.Number`` 182 | * Our project message: 183 | * ``ERROR: Attempted to use a sequence, but a number was expected.`` 184 | * Hint/Explanation: 185 | * 186 | -------------------------------------------------------------------------------- /exceptions/4clojure-prob134-AssertionError.ser: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Clojure-Intro-Course/clojure-intro-class/07fa408a825892be65e3ede9ce34e5b518cc324f/exceptions/4clojure-prob134-AssertionError.ser -------------------------------------------------------------------------------- /exceptions/4clojure-prob15-ClassCast.ser: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Clojure-Intro-Course/clojure-intro-class/07fa408a825892be65e3ede9ce34e5b518cc324f/exceptions/4clojure-prob15-ClassCast.ser -------------------------------------------------------------------------------- /exceptions/4clojure-prob156-AssertionError.ser: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Clojure-Intro-Course/clojure-intro-class/07fa408a825892be65e3ede9ce34e5b518cc324f/exceptions/4clojure-prob156-AssertionError.ser -------------------------------------------------------------------------------- /exceptions/4clojure-prob16-Arity.ser: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Clojure-Intro-Course/clojure-intro-class/07fa408a825892be65e3ede9ce34e5b518cc324f/exceptions/4clojure-prob16-Arity.ser -------------------------------------------------------------------------------- /exceptions/4clojure-prob17-NullPointer.ser: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Clojure-Intro-Course/clojure-intro-class/07fa408a825892be65e3ede9ce34e5b518cc324f/exceptions/4clojure-prob17-NullPointer.ser -------------------------------------------------------------------------------- /exceptions/4clojure-prob18-AssertionError.ser: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Clojure-Intro-Course/clojure-intro-class/07fa408a825892be65e3ede9ce34e5b518cc324f/exceptions/4clojure-prob18-AssertionError.ser -------------------------------------------------------------------------------- /exceptions/4clojure-prob20-AssertionError.ser: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Clojure-Intro-Course/clojure-intro-class/07fa408a825892be65e3ede9ce34e5b518cc324f/exceptions/4clojure-prob20-AssertionError.ser -------------------------------------------------------------------------------- /exceptions/4clojure-prob21-ArityException.ser: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Clojure-Intro-Course/clojure-intro-class/07fa408a825892be65e3ede9ce34e5b518cc324f/exceptions/4clojure-prob21-ArityException.ser -------------------------------------------------------------------------------- /exceptions/4clojure-prob23-IndexOutOfBounds.ser: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Clojure-Intro-Course/clojure-intro-class/07fa408a825892be65e3ede9ce34e5b518cc324f/exceptions/4clojure-prob23-IndexOutOfBounds.ser -------------------------------------------------------------------------------- /exceptions/4clojure-prob24-pre-plus-rewrite-ClassCast.ser: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Clojure-Intro-Course/clojure-intro-class/07fa408a825892be65e3ede9ce34e5b518cc324f/exceptions/4clojure-prob24-pre-plus-rewrite-ClassCast.ser -------------------------------------------------------------------------------- /exceptions/4clojure-prob27-ArityException.ser: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Clojure-Intro-Course/clojure-intro-class/07fa408a825892be65e3ede9ce34e5b518cc324f/exceptions/4clojure-prob27-ArityException.ser -------------------------------------------------------------------------------- /exceptions/4clojure-prob38-ArityException.ser: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Clojure-Intro-Course/clojure-intro-class/07fa408a825892be65e3ede9ce34e5b518cc324f/exceptions/4clojure-prob38-ArityException.ser -------------------------------------------------------------------------------- /exceptions/4clojure-prob57-ArityException.ser: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Clojure-Intro-Course/clojure-intro-class/07fa408a825892be65e3ede9ce34e5b518cc324f/exceptions/4clojure-prob57-ArityException.ser -------------------------------------------------------------------------------- /exceptions/4clojure-prob64-NullPointer.ser: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Clojure-Intro-Course/clojure-intro-class/07fa408a825892be65e3ede9ce34e5b518cc324f/exceptions/4clojure-prob64-NullPointer.ser -------------------------------------------------------------------------------- /exceptions/ArityException-1-1.ser: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Clojure-Intro-Course/clojure-intro-class/07fa408a825892be65e3ede9ce34e5b518cc324f/exceptions/ArityException-1-1.ser -------------------------------------------------------------------------------- /exceptions/ClassCast-1-1.ser: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Clojure-Intro-Course/clojure-intro-class/07fa408a825892be65e3ede9ce34e5b518cc324f/exceptions/ClassCast-1-1.ser -------------------------------------------------------------------------------- /exceptions/ClassCast-1-2.ser: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Clojure-Intro-Course/clojure-intro-class/07fa408a825892be65e3ede9ce34e5b518cc324f/exceptions/ClassCast-1-2.ser -------------------------------------------------------------------------------- /exceptions/DrRacket-Exercise2-ClassCast.ser: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Clojure-Intro-Course/clojure-intro-class/07fa408a825892be65e3ede9ce34e5b518cc324f/exceptions/DrRacket-Exercise2-ClassCast.ser -------------------------------------------------------------------------------- /exceptions/DrRacket-Exercise3-IndexOutOfBounds.ser: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Clojure-Intro-Course/clojure-intro-class/07fa408a825892be65e3ede9ce34e5b518cc324f/exceptions/DrRacket-Exercise3-IndexOutOfBounds.ser -------------------------------------------------------------------------------- /exceptions/DrRacket-Exercise9-ArityException.ser: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Clojure-Intro-Course/clojure-intro-class/07fa408a825892be65e3ede9ce34e5b518cc324f/exceptions/DrRacket-Exercise9-ArityException.ser -------------------------------------------------------------------------------- /exceptions/IllegalArgument-1-1.ser: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Clojure-Intro-Course/clojure-intro-class/07fa408a825892be65e3ede9ce34e5b518cc324f/exceptions/IllegalArgument-1-1.ser -------------------------------------------------------------------------------- /exceptions/IllegalArgument-1-2.ser: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Clojure-Intro-Course/clojure-intro-class/07fa408a825892be65e3ede9ce34e5b518cc324f/exceptions/IllegalArgument-1-2.ser -------------------------------------------------------------------------------- /exceptions/IndexOutOfBounds-1-1.ser: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Clojure-Intro-Course/clojure-intro-class/07fa408a825892be65e3ede9ce34e5b518cc324f/exceptions/IndexOutOfBounds-1-1.ser -------------------------------------------------------------------------------- /exceptions/NullPointer-1-1.ser: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Clojure-Intro-Course/clojure-intro-class/07fa408a825892be65e3ede9ce34e5b518cc324f/exceptions/NullPointer-1-1.ser -------------------------------------------------------------------------------- /exceptions/add-five-IllegalArgException.ser: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Clojure-Intro-Course/clojure-intro-class/07fa408a825892be65e3ede9ce34e5b518cc324f/exceptions/add-five-IllegalArgException.ser -------------------------------------------------------------------------------- /exceptions/classcast1.ser: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Clojure-Intro-Course/clojure-intro-class/07fa408a825892be65e3ede9ce34e5b518cc324f/exceptions/classcast1.ser -------------------------------------------------------------------------------- /exceptions/compilation_errors/#_must_be_followed_by_symbol.clj: -------------------------------------------------------------------------------- 1 | (ns compilation_errors.#_must_be_followed_by_symbol.clj) 2 | 3 | #2 4 | -------------------------------------------------------------------------------- /exceptions/compilation_errors/eof.clj: -------------------------------------------------------------------------------- 1 | (ns compilation_errors.eof) 2 | 3 | ;(defn eof-function[] (+ 2 3) 4 | -------------------------------------------------------------------------------- /exceptions/compilation_errors/first_argument_must_be_symbol.clj: -------------------------------------------------------------------------------- 1 | (ns compilation_errors.first_argument_must_be_symbol) 2 | 3 | ;(defn [coll] penultimate (last (drop-last coll))) 4 | -------------------------------------------------------------------------------- /exceptions/compilation_errors/greater_than_20_parameters.clj: -------------------------------------------------------------------------------- 1 | (ns compilation_errors.greater_than_20_parameters) 2 | 3 | (#(+ %22222 1) 2 3) 4 | 5 | ;(expect #"A function may not take more than more than 20 parameters." 6 | ; (get-text-no-location (run-and-catch-pretty-no-stacktrace (load-file compilation_errors.greater_than_20_parameters)))) 7 | 8 | ;(load-file compilation_errors.greater_than_20_parameters) 9 | -------------------------------------------------------------------------------- /exceptions/compilation_errors/greater_than_20_parameters2.clj: -------------------------------------------------------------------------------- 1 | (ns compilation_errors.greater_than_20_parameters2) 2 | 3 | (defn function_with_too_many_parameters [a b c d e f g h i j k l m n o p q r s t u v w x y z] 1) 4 | -------------------------------------------------------------------------------- /exceptions/compilation_errors/invalid_number.clj: -------------------------------------------------------------------------------- 1 | (ns compilation_errors.invalie_number) 2 | 3 | (+ 1.2.2 0) 4 | -------------------------------------------------------------------------------- /exceptions/compilation_errors/invalid_token_error1.clj: -------------------------------------------------------------------------------- 1 | (ns compilation_errors.invalid_token_error1) 2 | 3 | (/string "abcd") 4 | -------------------------------------------------------------------------------- /exceptions/compilation_errors/invalid_token_error2.clj: -------------------------------------------------------------------------------- 1 | (ns compilation_errors.invalid_token_error2) 2 | 3 | (defn # :) 4 | -------------------------------------------------------------------------------- /exceptions/compilation_errors/keyword_wrong_number_of_args.clj: -------------------------------------------------------------------------------- 1 | (ns compilation_errors.keyword_wrong_number_of_args) 2 | 3 | (:a) 4 | -------------------------------------------------------------------------------- /exceptions/compilation_errors/keyword_wrong_number_of_args2.clj: -------------------------------------------------------------------------------- 1 | (ns compilation_errors.keyword_wrong_number_of_args2) 2 | 3 | (let [f (if (= (+ 1 1) 2) (:a) +)] 4 | (f 1 2 3)) 5 | -------------------------------------------------------------------------------- /exceptions/compilation_errors/let-no-matching-pair.clj: -------------------------------------------------------------------------------- 1 | (ns exceptions.compilation_errors.let-no-matching-pair) 2 | 3 | (let [x] (+ x 2)) 4 | -------------------------------------------------------------------------------- /exceptions/compilation_errors/let-odd-number-bindings.clj: -------------------------------------------------------------------------------- 1 | (ns exceptions.compilation_errors.let-odd-number-bindings) 2 | 3 | (let (x 2)) 4 | -------------------------------------------------------------------------------- /exceptions/compilation_errors/loopevenforms.clj: -------------------------------------------------------------------------------- 1 | (ns compilation_errors.loopevenforms) 2 | 3 | ;(defn s [s] (loop [s])) 4 | -------------------------------------------------------------------------------- /exceptions/compilation_errors/non_closing_string.clj: -------------------------------------------------------------------------------- 1 | (ns compilation_errors.non_closing_string) 2 | 3 | "This string never closes 4 | 5 | -------------------------------------------------------------------------------- /exceptions/compilation_errors/only_recur_from_tail.clj: -------------------------------------------------------------------------------- 1 | (ns compilation_errors.only_recur_from_tail) 2 | 3 | ;(loop [x 10] (while (> x 1) (println x) (recur (- x 1)))) 4 | -------------------------------------------------------------------------------- /exceptions/compilation_errors/parameter_declaration.clj: -------------------------------------------------------------------------------- 1 | (ns compilation_errors.parameter_declaration) 2 | 3 | (defn square-this (input * input)) 4 | -------------------------------------------------------------------------------- /exceptions/compilation_errors/percent_followed_by_letter.clj: -------------------------------------------------------------------------------- 1 | (ns exceptions.compilation_errors.percent-followed-by-letter) 2 | 3 | (map #(+ %a 1) [1 2 3]) 4 | -------------------------------------------------------------------------------- /exceptions/compilation_errors/unable_to_resolve_squareThis.clj: -------------------------------------------------------------------------------- 1 | (ns compilation_errors.unable_to_resolve_squareThis) 2 | 3 | (fn squareThis [x] (* x x)) 4 | (squareThis 5) 5 | -------------------------------------------------------------------------------- /exceptions/compilation_errors/unable_to_resolve_symbol_hello.clj: -------------------------------------------------------------------------------- 1 | (ns compilation_errors.unable_to_resolve_symbol_hello) 2 | 3 | ;(print Hello World) 4 | print(Hello World) 5 | -------------------------------------------------------------------------------- /exceptions/compilation_errors/unable_to_resolve_world.clj: -------------------------------------------------------------------------------- 1 | (ns compilation_errors.unable_to_resolve_symbol_world) 2 | 3 | ;(print 'Hello World') 4 | -------------------------------------------------------------------------------- /exceptions/compilation_errors/unmatched_delim_re.clj: -------------------------------------------------------------------------------- 1 | (ns compilation_errors.unmatched_delim_re) 2 | 3 | (println #) 4 | -------------------------------------------------------------------------------- /exceptions/compilation_errors/unmatched_delimiter.clj: -------------------------------------------------------------------------------- 1 | (ns compilation_errors.unmatched_delimiter) 2 | 3 | ;(defn oops [] + 2)) 4 | -------------------------------------------------------------------------------- /exceptions/end_of_file.ser: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Clojure-Intro-Course/clojure-intro-class/07fa408a825892be65e3ede9ce34e5b518cc324f/exceptions/end_of_file.ser -------------------------------------------------------------------------------- /exceptions/generate1.clj: -------------------------------------------------------------------------------- 1 | (ns exceptions.generate1) 2 | 3 | ;################### 4 | ;### STATIC FILE ### 5 | ;## DO NOT CHANGE ## 6 | ;### DO NOT RUN #### 7 | ;################### 8 | 9 | ;*** Environment *** 10 | ; Windows 7 v.6.1.7601 , Intel i7 4700MQ @ 2.40ghz 4 cores (8 hyperthreaded), 8gb ram 11 | 12 | ;*** Functions *** 13 | (def path "exceptions/") 14 | 15 | (defn- run-and-catch 16 | "A function that takes quoted code and runs it, attempting to catch any exceptions it may throw. Returns the exeception or nil." 17 | [code] (try 18 | (eval code) 19 | (catch Exception e e))) 20 | 21 | (defn- export-to-file 22 | "Uses Java's Serializable to write a (java) object to a file" 23 | [obj filepath] 24 | (let [file-stream (java.io.FileOutputStream. filepath) 25 | obj-stream (java.io.ObjectOutputStream. file-stream) 26 | ] 27 | (.writeObject obj-stream obj) 28 | (.close obj-stream) 29 | (.close file-stream) 30 | (println (str "data saved in project folder or: " filepath)) 31 | )) 32 | 33 | (defn- import-from-file 34 | "Uses Java's Serializable to read a (java) object from a file" 35 | [filepath] 36 | (let [file-stream (java.io.FileInputStream. filepath) 37 | obj-stream (java.io.ObjectInputStream. file-stream) 38 | e (.readObject obj-stream)] 39 | (.close obj-stream) 40 | (.close file-stream) 41 | e)) 42 | 43 | (defn- write-objects-local 44 | "writes a java object to a file, creating it if it does not exist, in path (see errors.exceptions)" 45 | [object filename] 46 | (export-to-file object (str path filename))) 47 | 48 | (defn- read-objects-local 49 | "reads a file in path (see errors.exceptions) as a java-object" 50 | [filename] 51 | (import-from-file (str path filename))) 52 | 53 | ;*** file generation *** 54 | 55 | ;ClassCastException 56 | (comment 57 | 58 | (write-objects-local (run-and-catch '(inc "duck.")) "ClassCast-1-1.ser") 59 | 60 | (write-objects-local (run-and-catch '(+ 2 "goose.")) "ClassCast-1-2.ser") 61 | 62 | ;IllegalArgumentException 63 | 64 | (write-objects-local (run-and-catch '(cons 1 2)) "IllegalArgument-1-1.ser") 65 | 66 | (write-objects-local (run-and-catch '(contains? (seq [1 3 6]) 2)) "IllegalArgument-1-2.ser") 67 | 68 | ;IndexOutOfBoundsException 69 | 70 | (write-objects-local (run-and-catch '(nth [1 2 3] 5)) "IndexOutOfBounds-1-1.ser") 71 | 72 | ;ArityException 73 | 74 | (write-objects-local (run-and-catch '(conj 2)) "ArityException-1-1.ser") 75 | 76 | ;NullPointerException 77 | 78 | (write-objects-local (run-and-catch '(+ nil 2)) "NullPointer-1-1.ser") 79 | 80 | ) 81 | -------------------------------------------------------------------------------- /exceptions/unmatched_delimiter.ser: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Clojure-Intro-Course/clojure-intro-class/07fa408a825892be65e3ede9ce34e5b518cc324f/exceptions/unmatched_delimiter.ser -------------------------------------------------------------------------------- /installation_instructions.md: -------------------------------------------------------------------------------- 1 | Introduction-to-LightTable 2 | ========================== 3 | 4 | A guide to setting up and using the features of LightTable. 5 | 6 | Our cloning url is to the right, or you can just click Clone in Desktop to get a copy of this repository on your personal computer. (If you have git installed, go ahead and clone it in the command line). 7 | 8 | #Set Up of Java 9 | 10 | Firstly, you should have Java, and the Java Developers Kit, already downloaded on your computer. If that isn't installed already, go to http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html and click the JDK option for your operating system. Follow the given directions for completing this download. 11 | 12 | #Set Up of Leiningen 13 | 14 | You can download Leiningen from http://leiningen.org/. Downloading Leiningen can be tricky, so be patient. Start by downloading the lein script. If when trying to download the lein script, the script just opens (instead of downloading), first try right-clicking and saving it as lein to a place such as the desktop. If that doesn't work, then try making a new text file and copy-pasting the whole script into the new file on your computer and saving it as lein to a location such as the desktop. Then cd into the place you've saved the file, such as desktop. Then move it using the terminal by typing 15 | 16 | ```bash 17 | sudo mv lein /usr/bin/lein 18 | ``` 19 | 20 | Then, make it executable by typing 21 | 22 | ```bash 23 | chmod a+x usr/bin/lein 24 | ``` 25 | 26 | into the command line. After this, then type 27 | 28 | ```bash 29 | lein 30 | ``` 31 | 32 | into the command line, and watch it self-install a bunch of things. Then, type lein once more, and when a list of leiningen commands appear, you know that leiningen is fully installed! 33 | 34 | #Set Up of LightTable 35 | 36 | You can download LightTable from http://www.lighttable.com/. Downloading LightTable is pretty easy. Follow the steps on the LightTable download site, move the folder that appears to the location you want on your computer, and you're set to go! 37 | 38 | #Set Up of Autoexpect 39 | 40 | For expectations, there is a little bit of formatting needed to make sure everything works correctly. You can make a new project by cd-ing into the desired directory and then typing: 41 | 42 | ``` bash 43 | lein new your-project-name 44 | ``` 45 | 46 | into the command line. We've named our project: test-project. It should say that a new project has been created. Open LightTable and open the Workspace by clicking View -> Workspace. Right click on the workspace area and click Add Folder. Find where you've put your project and click Upload. Open the file project.clj by single-clicking on the name of your project and then single-clicking on project.clj. What's currently in the file should look something like this: 47 | 48 | ```bash 49 | (defproject test-project "1.0.0-SNAPSHOT" 50 | :description "FIXME: write description" 51 | :dependencies [[org.clojure/clojure "1.3.0"]]) 52 | ``` 53 | 54 | Change your project.clj file so it looks like this: 55 | 56 | ```bash 57 | (defproject test-project "1.0.0-SNAPSHOT" 58 | :description "A project for showing basic uses of LightTable and Autoexpect" 59 | :dependencies [[org.clojure/clojure "1.5.1"] 60 | [expectations "2.0.6"]] 61 | :plugins [[lein-autoexpect "1.0"]]) 62 | ``` 63 | 64 | making sure that you add the expectations dependency and the lein-autoexpect plugin; the description can be whatever you like. Save the file. Next, open up your test file by clicking test/ and clicking through as many nested folders as needed to get to core_test.clj. Once open, your project should look something like this: 65 | 66 | ```bash 67 | (ns test-project.core-test 68 | (:require [clojure.test :refer :all] 69 | [test-project.core :refer :all])) 70 | 71 | (deftest a-test 72 | (testing "FIXME, I fail." 73 | (is (= 0 1)))) 74 | ``` 75 | 76 | Replace the phrase clojure.test to the word expectations. It should look something like this: 77 | 78 | ```bash 79 | (ns test-project.core-test 80 | (:require [expectations :refer :all] 81 | [test-project.core :refer :all])) 82 | 83 | (deftest a-test 84 | (testing "FIXME, I fail." 85 | (is (= 0 1)))) 86 | ``` 87 | 88 | Now, you can erase the call to deftest, replacing it with something along the lines of (expect 5 (+ 2 3)). This is the most basic form of testing using autoexpect. Now, be sure you save the file and then go to the command line, cd into the project directory, and type 89 | 90 | ```bash 91 | lein autoexpect 92 | ``` 93 | 94 | and then hit enter. Some extra things might print; you can ignore those. At the end, it should say something like: 95 | 96 | ```bash 97 | Ran 1 tests containing 1 assertions in 14 msecs 98 | 0 failures, 0 errors. 99 | ``` 100 | 101 | It might even use pretty colors! 102 | 103 | Now you can happily code in LightTable and every time you save, your tests will run automatically and show the results in the terminal. Note, if you want autoexpect to stop running, hit Ctrl+c (or Ctrl+z on Mac). 104 | 105 | Happy coding! 106 | 107 | For more information on Autoexpect, take a look at this link: http://jayfields.com/expectations/installing.html 108 | 109 | #Uninstalling the Programs Listed Above 110 | 111 | If you ever would like to uninstall any of the software explained above, then feel free to follow these steps. 112 | 113 | Java: If you would like to uninstall Java (although we have no idea why you would feel the need to), you can go to this site: 114 | 115 | ```bash 116 | java.com/remove 117 | ``` 118 | and follow the directions to uninstall Java. 119 | 120 | Leiningen: First, you must make sure that you delete both the .lein hidden file. This would probably be somewhere like your home, but it could be other places. If needed, use the command 121 | 122 | ```bash 123 | ls -a 124 | ``` 125 | 126 | (on mac and linux) or 127 | 128 | ```bash 129 | dir /a 130 | ``` 131 | (on windows). Then, assuming that lein is in your /usr/bin, you can go ahead and do 132 | 133 | ```bash 134 | sudo rm /usr/bin/lein 135 | ``` 136 | to delete the lein folder from your /usr/bin. Check that leiningen is fully uninstalled by typing 137 | 138 | ```bash 139 | lein 140 | ``` 141 | 142 | into your command line. No leiningen commands should appear. 143 | 144 | LightTable: On windows, you can use the uninstall tool, otherwise, your best bet would be to just drag it to trash and then empty your trash. 145 | 146 | Autoexpect: Autoexpect isn't a thing we really "installed," so therefore, there is no uninstalling. If you would like to stop using Autoexpect, just stop using it. Technically, Expectations and Autoexpect are just part of Leiningen. 147 | 148 | Any comments and/or confusions? We'd love to hear from you! Email saxxx027@morris.umn.edu if you have any concerns! 149 | -------------------------------------------------------------------------------- /project.clj: -------------------------------------------------------------------------------- 1 | (defproject intro "0.2.3" 2 | :description "A pilot project to use Clojure for introductory computer science courses at the University of Minnesota - Morris" 3 | :dependencies [ 4 | [org.clojure/clojure "1.9.0-alpha10"] 5 | [clj-stacktrace "0.2.8"] ;;No updates for over a year 6 | [org.clojure/core.incubator "0.1.2"] ;;No updates for over a year 7 | [seesaw "1.4.5"] 8 | [expectations "2.1.1"]] 9 | :plugins [[lein-expectations "0.0.7"] 10 | [lein-autoexpect "1.0"] 11 | [lein-pprint "1.1.2"]] 12 | :main intro.core) 13 | -------------------------------------------------------------------------------- /src/corefns/assert_handling.clj: -------------------------------------------------------------------------------- 1 | (ns corefns.assert_handling 2 | ;; seqable? is in core since Clojure veresion 1.9 3 | ;; (:use [clojure.core.incubator]) 4 | (:require [corefns.failed_asserts_info :refer :all])) 5 | 6 | 7 | ;;; Functions to check pre-conditions and record the offenders for 8 | ;;; error processing. 9 | 10 | 11 | (defn check-if-seqable? 12 | "returns true if x is seqable and false otherwise, sets data 13 | in seen-failed-asserts. If the second argument is present, it's added 14 | to the seen-failed-asserts as the number of the argument" 15 | [fname x & [n]] 16 | (if (seqable? x) true 17 | (do (add-to-seen {:check "a sequence" 18 | :class (class x) 19 | :value x 20 | :fname fname}) 21 | (if n (add-to-seen {:arg-num n})) 22 | false))) 23 | ; (cond 24 | ; (number? n) (add-to-seen {:arg-num n}) 25 | ; (string? n) (add-to-seen {:number-of-args (read-string n)})) 26 | ; (if n 27 | ; (if (number? n) (add-to-seen {:arg-num n}) 28 | ; (if (string? n) (add-to-seen {:number-of-args (read-string n)}))) 29 | ; (if q (add-to-seen {:number-of-args (read-string n)})) 30 | 31 | ;this is currently unused 32 | (defn check-if-sequential? 33 | "returns true if x is sequential and false otherwise, sets data 34 | in seen-failed-asserts. If the second argument is present, it's added 35 | to the seen-failed-asserts as the number of the argument" 36 | [fname x & [n]] 37 | (if (sequential? x) true 38 | (do (add-to-seen {:check "a sequence" 39 | :class (class x) 40 | :value x 41 | :fname fname}) 42 | (if n (add-to-seen {:arg-num n})) 43 | false))) 44 | 45 | ;i just added this, needs tests, used in assoc pre-conditions 46 | (defn check-if-map-or-vector? [fname x & [n]] 47 | (if (or (vector? x) (map? x) (nil? x)) true 48 | (do (add-to-seen {:check "a map or vector" 49 | :class (class x) 50 | :value x 51 | :fname fname}) 52 | (if n (add-to-seen {:arg-num n})) 53 | false))) 54 | 55 | (defn check-if-map? [fname x & [n]] 56 | (if (or (map? x) (nil? x)) true 57 | (do (add-to-seen {:check "a map" 58 | :class (class x) 59 | :value x 60 | :fname fname}) 61 | (if n (add-to-seen {:arg-num n})) 62 | false))) 63 | 64 | (defn check-if-function? [fname x & [n]] 65 | (if (ifn? x) true 66 | (do (add-to-seen {:check "a function" 67 | :class (class x) 68 | :value x 69 | :fname fname}) 70 | (if n (add-to-seen {:arg-num n})) 71 | false))) 72 | 73 | (defn check-if-number? [fname x & [n]] 74 | (if (number? x) true 75 | (do (add-to-seen {:check "a number" 76 | :class (class x) 77 | :value x 78 | :fname fname}) 79 | (if n (add-to-seen {:arg-num n})) 80 | false))) 81 | 82 | (defn check-if-integer? [fname x & [n]] 83 | (if (integer? x) true 84 | (do (add-to-seen {:check "an integer number" 85 | :class (class x) 86 | :value x 87 | :fname fname}) 88 | (if n (add-to-seen {:arg-num n})) 89 | false))) 90 | 91 | (defn check-if-string? [fname x & [n]] 92 | (if (string? x) true 93 | (do (add-to-seen {:check "a string" 94 | :class (class x) 95 | :value x 96 | :fname fname}) 97 | (if n (add-to-seen {:arg-num n})) 98 | false))) 99 | 100 | (defn check-if-character? [fname x & [n]] 101 | (if (char? x) true 102 | (do (add-to-seen {:check "a character" 103 | :class (class x) 104 | :value x 105 | :fname fname}) 106 | (if n (add-to-seen {:arg-num n})) 107 | false))) 108 | 109 | (defn check-if-var? [fname x & [n]] 110 | (if (var? x) true 111 | (do (add-to-seen {:check "a var" 112 | :class (class x) 113 | :value x 114 | :fname fname}) 115 | (if n (add-to-seen (:arg-num n))) 116 | false))) 117 | 118 | (defn check-if-string-or-nil? [fname x & [n]] 119 | (if (or (instance? CharSequence x) (nil? x)) true 120 | (do (add-to-seen {:check "a string or nil" 121 | :class (class x) 122 | :value x 123 | :fname fname}) 124 | (if n (add-to-seen {:arg-num n})) 125 | false))) 126 | 127 | (defn check-if-regex? [fname x & [n]] 128 | (if (= (class x) java.util.regex.Pattern) true 129 | (do (add-to-seen {:check "a regular expression" 130 | :class (class x) 131 | :value x 132 | :fname fname}) 133 | (if n (add-to-seen {:arg-num n})) 134 | false))) 135 | 136 | (defn check-if-string-or-character? [fname x & [n]] ;for string-contains? 137 | (if (or (string? x) (char? x)) true 138 | (do (add-to-seen {:check "either a string or character" 139 | :class (class x) 140 | :value x 141 | :fname fname}) 142 | (if n (add-to-seen {:arg-num n})) 143 | false))) 144 | 145 | ;; should pass the starting arg number: it's different for different functions 146 | (defn check-if-seqables? [fname arguments start] 147 | (loop [args arguments n start] 148 | (if (empty? args) 149 | true 150 | (if (not (check-if-seqable? fname (first args) n)) 151 | false 152 | (recur (rest args) (inc n)))))) 153 | 154 | ;; should pass the starting arg number: it's different for different functions 155 | (defn check-if-numbers? [fname arguments start] 156 | (loop [args arguments n start] 157 | (if (empty? args) 158 | true 159 | (if (not (check-if-number? fname (first args) n)) 160 | false 161 | (recur (rest args) (inc n)))))) 162 | 163 | ;; should pass the starting arg number: it's different for different functions 164 | (defn check-if-strings? [fname arguments start] 165 | (loop [args arguments n start] 166 | (if (empty? args) 167 | true 168 | (if (not (check-if-string? fname (first args) n)) 169 | false 170 | (recur (rest args) (inc n)))))) 171 | 172 | ;; allows for us to not say nth arg when there is only one arg 173 | (defn only-arg [arg] (if arg arg (when-not arg (add-to-seen {:only-arg true}) arg))) 174 | 175 | ;; should pass the starting arg number: it's different for different functions (not sure if true for this one) 176 | (defn check-if-functions? [fname arguments start] 177 | (loop [args arguments n start] 178 | (if (empty? args) 179 | true 180 | (if (not (check-if-function? fname (first args) n)) 181 | false 182 | (recur (rest args) (inc n)))))) 183 | 184 | ;~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 185 | 186 | ;(defn all-elems-are-map-or-vector? [coll] 187 | ; (every? #(or (vector? %) (map? %)) coll)) 188 | ; 189 | ;(defn all-elems-have-length-two? [coll] 190 | ; (every? #(= (count %) 2) coll)) ;with hashmaps, this function will ALWAYS return true because it breaks up hashmaps into 191 | ; ;vectors of 2, so it will only return false if the hashmap is not a valid hashmap, since 192 | ; ;hashmaps always are built up of pairs of 2 193 | ; 194 | ;(defn all-elems-are-map-or-vector-with-length-2? [fname coll & [n]] 195 | ; (if (and (all-elems-are-map-or-vector? coll) 196 | ; (all-elems-have-length-two? coll)) 197 | ; true ;return true 198 | ; (do (add-to-seen {:check "either a hashmap, or a collection of vectors or hashmaps of length 2," 199 | ; :class (class coll) ;else add-to seen 200 | ; :value coll 201 | ; :fname fname}) 202 | ; (if n (add-to-seen {:arg-num n})) 203 | ; false))) ;and return false 204 | ; 205 | ;(defn check-if-can-convert-to-hashmap [fname arg1 arg2 & [n]] 206 | ; (if (map? arg1) ;is arg1 a hashmap? 207 | ; (if (map? arg2) ;if so, is arg2 a hashmap? 208 | ; true ;if both args are hashmaps, return true 209 | ; (all-elems-are-map-or-vector-with-length-2? fname arg2 n)) ;if arg1 is a hashmap, but not arg2, do all-elems-are-map-or-vector-with-length-2? 210 | ; true)) ;if arg1 is NOT a hashamp, return true 211 | -------------------------------------------------------------------------------- /src/corefns/collection_fns.clj: -------------------------------------------------------------------------------- 1 | (ns corefns.collection_fns 2 | (:require [corefns.assert_handling :refer :all] 3 | [corefns.failed_asserts_info :refer :all])) 4 | 5 | 6 | ;;; Beginner-friendly functions for type-independent sequence handling. 7 | 8 | 9 | (defn add-first [argument1 argument2] 10 | {:pre [(check-if-seqable? "add-first" argument1)]} 11 | (cons argument2 argument1)) 12 | 13 | (defn add-last [argument1 argument2] 14 | {:pre [(check-if-seqable? "add-last" argument1)]} 15 | (doall (concat argument1 [argument2]))) 16 | 17 | ;; user-friendly versions of confusing functions 18 | (defn contains-value? [coll x] 19 | {:pre [(check-if-seqable? "contains-value?" coll 1)]} 20 | (let [values (if (map? coll) (vals coll) coll)] 21 | (not (every? #(not= x %) values)))) 22 | 23 | (defn contains-key? [coll key] 24 | {:pre [(check-if-seqable? "contains-key?" coll 1)]} 25 | (clojure.core/contains? coll key)) 26 | 27 | ;; more content tests 28 | (defn any? [pred coll] ;;unsure how to check if somethings a predicate 29 | {:pre [(check-if-function? "any?" pred 1) 30 | (check-if-seqable? "any?" coll 2)]} 31 | (not (not-any? pred coll))) 32 | ; yes, I know :-( 33 | ;(defn some? [pred coll] 34 | ; (not (not-any? pred coll))) 35 | -------------------------------------------------------------------------------- /src/corefns/failed_asserts_info.clj: -------------------------------------------------------------------------------- 1 | (ns corefns.failed_asserts_info) 2 | 3 | 4 | ;;; a global hashmap of recorded types/messages 5 | 6 | 7 | (def seen-failed-asserts (atom {})) 8 | 9 | (defn add-to-seen 10 | "adds bindings to seen objects" 11 | [binds] 12 | (swap! seen-failed-asserts merge binds)) ; merge overwrites the same fields but adds new ones 13 | 14 | (defn empty-seen 15 | "removes all bindings from seen objects" 16 | [] 17 | (swap! seen-failed-asserts {})) 18 | -------------------------------------------------------------------------------- /src/corefns/specs.clj: -------------------------------------------------------------------------------- 1 | (ns corefns.specs 2 | (require [clojure.spec :as s] 3 | [clojure.spec.test :as stest])) 4 | 5 | 6 | ;;; Spec definitions and instruments for corefns functions 7 | 8 | 9 | ;; Spec for clojure.spec by Tony 10 | ;; =======================================================================================| 11 | ;; * For every function with inlining, we can't use spec without overwriting it. | 12 | ;; | 13 | ;; * put spec before a funtion -> specify conditions for the core function | 14 | ;; vs | 15 | ;; put spec after a function -> specify conditions for the overwritten function | 16 | ;; | 17 | ;; * There are two cases that we need to put spec after we overwrite a function | 18 | ;; 1. When the function has different numbers of :inline-arities like #{2 3} | 19 | ;; 2. When the function has only :inline without :inline-arities | 20 | ;; | 21 | ;; * s/cat gives a key(name) to each element in a sequence and returns a hashmap | 22 | ;; | 23 | ;; * To get the function name, we first need to attach the spec to the core function | 24 | ;; and then overwrite them (overwriting first doesn't work) | 25 | ;; 1. Overwritten map : #object[corefns.corefns$map ... ] | 26 | ;; 2. Not overwritten map: #object[clojure.spec.test$spec_checking_fn$fn__12959 ... ] | 27 | ;; | 28 | ;; * 'NO' means the function doesn't need to be overwritten | 29 | ;; 'O' means the function should be overwritten | 30 | ;; =======================================================================================| 31 | 32 | 33 | ;; ####################################### 34 | ;; # helper functions/predicates # 35 | ;; ####################################### 36 | (defn length1? [coll] (= (count coll) 1)) 37 | (defn length2? [coll] (= (count coll) 2)) 38 | (defn length3? [coll] (= (count coll) 3)) 39 | (defn length-greater0? [coll] (> (count coll) 0)) 40 | (defn length-greater1? [coll] (> (count coll) 1)) 41 | (defn length-greater2? [coll] (> (count coll) 2)) 42 | 43 | (s/def ::length-one length1?) 44 | (s/def ::length-two length2?) 45 | (s/def ::length-three length3?) 46 | (s/def ::length-greater-zero length-greater0?) 47 | (s/def ::length-greater-one length-greater1?) 48 | (s/def ::length-greater-two length-greater2?) 49 | 50 | 51 | ;; ####################################### 52 | ;; # specs # 53 | ;; ####################################### 54 | 55 | ; ##### NO ##### 56 | (s/fdef clojure.core/empty? 57 | :args (s/and ::length-one 58 | (s/cat :check-seqable seqable?))) 59 | (stest/instrument 'clojure.core/empty?) 60 | 61 | ; ##### NO ##### 62 | (s/fdef clojure.core/map 63 | :args (s/and ::length-greater-one 64 | (s/cat :check-function ifn? :check-seqable (s/+ seqable?)))) 65 | (stest/instrument 'clojure.core/map) 66 | 67 | 68 | ; ##### NO ##### 69 | (s/fdef clojure.core/conj 70 | :args (s/and ::length-greater-one 71 | (s/cat :check-seqable seqable? :dummy (s/+ any?)))) 72 | (stest/instrument 'clojure.core/conj) 73 | 74 | ; ##### NO ##### 75 | (s/fdef clojure.core/into 76 | :args (s/and ::length-two 77 | (s/cat :check-seqable seqable? :check-seqable seqable?))) 78 | (stest/instrument 'clojure.core/into) 79 | 80 | ; ##### NO ##### 81 | (s/fdef clojure.core/cons 82 | :args (s/and ::length-two 83 | (s/cat :dummy any? :check-seqable seqable?))) 84 | (stest/instrument 'clojure.core/cons) 85 | 86 | ; ##### NO ##### 87 | ; This doesn't differenciate two and three arity cases - issue #116 88 | ;; (s/fdef reduce 89 | ;; :args (s/cat :check-funtion ifn? :dummy (s/? ::s/any) :check-seqable seqable?)) 90 | ; Doesn't work - s/cat returns a hashmap 91 | ;; (s/fdef reduce 92 | ;; :args (s/and (s/or :two (s/cat :dummy ::s/any :dummy ::s/any) 93 | ;; :three (s/cat :dummy ::s/any :dummy ::s/any :dummy ::s/any)) 94 | ;; (s/or :three-args (s/cat :check-function ifn? :dummy ::s/any :check-seqable seqable?) 95 | ;; :two-args (s/cat :check-function ifn? :check-seqable seqable?)))) 96 | ;; (s/fdef reduce 97 | ;; :args (s/or 98 | ;; :three (s/cat :check-function ifn? :dummy ::s/any :check-seqable seqable?) 99 | ;; :two (s/cat :check-function ifn? :check-seqable seqable?))) 100 | 101 | ;; (s/fdef reduce 102 | ;; :args (s/or :two-case (s/and ::length-two 103 | ;; (s/cat :check-function ifn? :check-seqable seqable?)) 104 | ;; :three-case (s/and ::length-three 105 | ;; (s/cat :check-function ifn? :dummy ::s/any :check-seqable seqable?)))) 106 | ;; (s/instrument #'reduce) 107 | (s/fdef clojure.core/reduce 108 | :args (s/or :two-case (s/and ::length-two 109 | (s/cat :check-function ifn? :check-seqable seqable?)) 110 | :three-case (s/and ::length-three 111 | (s/cat :check-function ifn? :dummy any? :check-seqable seqable?)))) 112 | (stest/instrument 'clojure.core/reduce) 113 | 114 | ; ##### O ##### - doesn't work unless the spec is after the overwritten function -> handled 115 | ;; (s/fdef clojure.core/nth 116 | ;; :args (s/or :two-case (s/and ::length-two 117 | ;; (s/cat :check-seqable seqable? :check-number number?)) 118 | ;; :three-case (s/and ::length-three 119 | ;; (s/cat :check-seqable seqable? :check-number number? :dummy any?)))) 120 | ;; (stest/instrument 'clojure.core/nth) 121 | 122 | 123 | ;; List of functions that have specs and aren't overwritten: 124 | ; ##### NO ##### 125 | (s/fdef clojure.core/filter 126 | :args (s/and ::length-two 127 | (s/cat :check-function ifn? :check-seqable seqable?))) 128 | (stest/instrument 'clojure.core/filter) 129 | 130 | 131 | ; ##### NO ##### 132 | (s/fdef clojure.core/mapcat 133 | :args (s/and ::length-greater-one 134 | (s/cat :check-function ifn? :check-seqable (s/+ seqable?)))) 135 | (stest/instrument 'clojure.core/mapcat) 136 | 137 | ; ##### NO ##### 138 | ; We need s/nilable here because map? and vector? return false for nil 139 | (s/fdef clojure.core/assoc 140 | :args (s/and ::length-greater-two 141 | (s/cat :check-map-or-vector (s/or :check-map (s/nilable map?) :check-vector vector?) 142 | :dummy any? 143 | :dummies (s/+ any?)))) 144 | (stest/instrument 'clojure.core/assoc) 145 | 146 | ; ##### NO ##### 147 | ; We need s/nilable here because map? returns false for nil 148 | (s/fdef clojure.core/dissoc 149 | :args (s/and ::length-greater-zero 150 | (s/cat :check-map (s/nilable map?) :dummies (s/* any?)))) 151 | (stest/instrument 'clojure.core/dissoc) 152 | 153 | ; ##### NO ##### 154 | (s/fdef clojure.core/odd? 155 | :args (s/and ::length-one 156 | (s/cat :check-integer integer?))) 157 | (stest/instrument 'clojure.core/odd?) 158 | 159 | ; ##### NO ##### 160 | (s/fdef clojure.core/even? 161 | :args (s/and ::length-one 162 | (s/cat :check-integer integer?))) 163 | (stest/instrument 'clojure.core/even?) 164 | 165 | ; ##### O ##### 166 | (s/fdef clojure.core/< 167 | :args (s/and ::length-greater-zero 168 | (s/cat :check-number (s/+ number?)))) 169 | (stest/instrument 'clojure.core/<) 170 | 171 | ; ##### O ##### 172 | (s/fdef clojure.core/+ 173 | :args (s/cat :check-number (s/* number?))) 174 | (stest/instrument 'clojure.core/+) 175 | 176 | ; ##### O ##### 177 | (s/fdef clojure.core/- 178 | :args (s/cat :check-number (s/+ number?))) 179 | (stest/instrument 'clojure.core/-) 180 | 181 | ; ##### O ##### - TODO: doesn't work. the same behavior as nth -> handled 182 | ;; (s/fdef clojure.core/quot 183 | ;; :args (s/and ::length-two 184 | ;; (s/cat :check-number number? :check-number number?))) 185 | ;; (stest/instrument 'clojure.core/quot) 186 | 187 | ; ##### NO ##### 188 | (s/fdef clojure.core/comp 189 | :args (s/cat :check-function (s/* ifn?))) 190 | (stest/instrument 'clojure.core/comp) 191 | 192 | ; ##### NO ##### 193 | (s/fdef clojure.core/repeatedly 194 | :args (s/or :one-case (s/and ::length-one 195 | (s/cat :check-function ifn?)) 196 | :two-case (s/and ::length-two 197 | (s/cat :check-number number? :check-function ifn?)))) 198 | (stest/instrument 'clojure.core/repeatedly) 199 | 200 | ; ##### NO ##### 201 | (s/fdef clojure.core/repeat 202 | :args (s/or :one-case ::length-one 203 | :two-case (s/and ::length-two 204 | (s/cat :check-number number? :dummy any?)))) 205 | (stest/instrument 'clojure.core/repeat) 206 | 207 | ; ##### NO ##### 208 | (s/fdef clojure.core/distinct 209 | :args (s/and ::length-one 210 | (s/cat :check-seqable seqable?))) 211 | (stest/instrument 'clojure.core/distinct) 212 | 213 | ;; This hashmap is used to get function names because 'speced' functions are stored differently 214 | (def specs-map 215 | {(str map) "map", (str filter) "filter", (str reduce) "reduce", (str empty?) "empty?", 216 | (str conj) "conj", (str into) "into", (str cons) "cons", (str mapcat) "mapcat", 217 | (str assoc) "assoc", (str dissoc) "dissoc", (str odd?) "odd?", (str even?) "even?", 218 | (str <) "<", (str comp) "comp", (str repeatedly) "repeatedly", (str repeat) "repeat", 219 | (str distinct) "distinct"}) 220 | -------------------------------------------------------------------------------- /src/errors/dictionaries.clj: -------------------------------------------------------------------------------- 1 | (ns errors.dictionaries 2 | (:use [corefns.corefns] 3 | [errors.messageobj] 4 | [corefns.specs :only [specs-map]] 5 | [corefns.failed_asserts_info] 6 | )) 7 | (require '[clojure.string :as cs]) 8 | 9 | 10 | ;;; A dictionary of known types and their user-friendly representations. 11 | ;;; Potentially, we can have multiple dictionaries depending on the level. 12 | 13 | 14 | (def type-dictionary {:java.lang.String "a string" 15 | :java.lang.CharSequence "a string" 16 | :java.lang.Number "a number" 17 | :clojure.lang.Keyword "a keyword" 18 | :java.lang.Boolean "a boolean" 19 | ;; I think this is better for new students to lump all numbers together 20 | :java.lang.Long "a number" 21 | :java.lang.Integer "a number" 22 | :java.lang.Double "a number" 23 | :java.lang.Float "a number" 24 | :java.lang.Short "a number" 25 | :clojure.lang.BigInt "a number" 26 | ;; perhaps add big ints and such 27 | :java.lang.Character "a character" ;; switched back from a symbol 28 | ;; to short-cut processing of error messages for 29 | ;; "Don't know how to create a sequence from ..." 30 | :clojure.lang.ISeq "a sequence" 31 | :ISeq "a sequence" 32 | ;; Refs come up in turtle graphics 33 | :clojure.lang.Ref "a mutable object" 34 | ;; regular expressions wouldn't make sense to beginners, 35 | ;; but it's better to recognize their types for easier 36 | ;; help with diagnostics 37 | :java.util.regex.Pattern "a regular expression pattern" 38 | :java.util.regex.Matcher "a regular expression matcher" 39 | ;; also not something beginners would know, 40 | ;; but useful for understanding errors 41 | :clojure.lang.Symbol "a symbol" 42 | :clojure.lang.IPersistentStack "an object that behaves as a stack (such as a vector or a list)" 43 | :clojure.lang.PersistentArrayMap "a map" 44 | ;; assoc works on maps and vectors: 45 | :clojure.lang.Associative "a map or a vector" 46 | :clojure.lang.Reversible "a vector or a sorted-map" 47 | :clojure.lang.Sorted "a collection stored in a sorted manner (such as sorted-map or sorted-set)" 48 | :clojure.lang.Sequential "a sequential collection (such as a vector or a list)" 49 | ;; This is here because of shuffle. It's not ideal, too similar to Sequential 50 | :java.util.Collection " a traversable collection (such as a vector, list, or set)" ; not sure if this makes sense in relation to the previous one 51 | ;; got this in a seesaw error message. Not sure what other types are "Named" 52 | ;; source: https://groups.google.com/forum/?fromgroups#!topic/clojure/rd-MDXvn3q8 53 | :clojure.lang.Named "a keyword or a symbol" 54 | :clojure.lang.nil "nil"}) 55 | 56 | 57 | ;; matching type interfaces to beginner-friendly names. 58 | ;; Note: since a type may implement more than one interface, 59 | ;; the order is essential. The lookup is done in order, so 60 | ;; the first match is returned. 61 | ;; That's why it's a vector, not a hashmap. 62 | ;; USE CAUTION WHEN ADDING NEW TYPES! 63 | 64 | (def general-types [[Number "a number"] 65 | [clojure.lang.IPersistentVector "a vector"] 66 | [clojure.lang.IPersistentList "a list"] 67 | [clojure.lang.IPersistentSet "a set"] 68 | [clojure.lang.IPersistentMap "a map"] 69 | [clojure.lang.ISeq "a sequence"] 70 | ;; collections - must go before functions since some collections 71 | ;; implement the IFn interface 72 | [clojure.lang.IPersistentCollection "a collection"] 73 | [clojure.lang.IFn "a function"]]) 74 | 75 | ;; The best approximation of a type t not listed in the type-dictionary (as a string) 76 | ;;; best-approximation: type -> string 77 | (defn best-approximation [t] 78 | "returns a string representation of a type t not listed in the type-dictionary for user-friendly error messages" 79 | (let [attempt (resolve (symbol t)) 80 | type (if attempt attempt (clojure.lang.RT/loadClassForName (str "clojure.lang." t))) ;; may need to add clojure.lang. for some types. 81 | matched-type (if type (first (filter #(isa? type (first %)) general-types)))] 82 | (if matched-type (second matched-type) (str "unrecognized type " t)))) 83 | 84 | ;;; get-type: type -> string 85 | (defn get-type 86 | "returns a user-friendly representation of a type if it exists in the type-dictionary, 87 | or its default representation as an unknown type" 88 | [t] 89 | ((keyword t) type-dictionary (best-approximation t))) 90 | 91 | (defn get-type-with-nil 92 | "Takes a value that can be nil and returns its type in a user-readable form" 93 | [v] 94 | (if (nil? v) "nil" (get-type (.getName (type v))))) 95 | 96 | 97 | ;; hashmap of internal function names and their user-friendly versions 98 | (def predefined-names {:_PLUS_ "+" :_ "-" :_SLASH_ "/" }) 99 | 100 | ;;; lookup-funct-name: predefined function name -> string 101 | (defn lookup-funct-name 102 | "looks up pre-defined function names, such as _PLUS_. If not found, 103 | returns the original" 104 | [fname] 105 | (let [lookup ((keyword fname) predefined-names)] 106 | (if lookup lookup (-> fname 107 | (clojure.string/replace #"_QMARK_" "?") 108 | (clojure.string/replace #"_BANG_" "!") 109 | (clojure.string/replace #"_EQ_" "=") 110 | (clojure.string/replace #"_LT_" "<") 111 | (clojure.string/replace #"_GT_" ">") 112 | (clojure.string/replace #"_STAR_" "*"))))) 113 | 114 | ;;; check-if-anonymous-function: string -> string 115 | (defn check-if-anonymous-function 116 | "Takes a string as function name and returns a string \"anonymous function\" 117 | if it is an anonymous function, its name otherwise" 118 | [fname] 119 | (if (or (= fname "fn") (re-matches #"fn_(.*)" fname) (re-matches #"fn-(.*)" fname)) 120 | "anonymous-function" fname)) 121 | 122 | ;;; get-match-name: string -> string 123 | (defn get-match-name 124 | "extract a function name from a qualified name" 125 | [fname] 126 | (let [check-spec ((merge corefns-map specs-map) fname) 127 | m (if check-spec check-spec (nth (re-matches #"(.*)\$(.*)" fname) 2)) 128 | matched (if m m (nth (re-matches #"(.*)/(.*)" fname) 2))] 129 | (if matched 130 | (check-if-anonymous-function (lookup-funct-name matched)) 131 | fname))) 132 | 133 | ;;; remove-inliner: string -> string 134 | (defn- remove-inliner 135 | "If fname ends with inliner this will return everything before it" 136 | [fname] 137 | (let [match (nth (re-matches #"(.*)--inliner(.*)" fname) 1)] 138 | (if match match fname))) 139 | 140 | ;;; get-function-name: string -> string 141 | (defn get-function-name 142 | [fname] 143 | "Returns a human-readable unqualified name of the function, 144 | or \"anonymous function\" if no name is found." 145 | (remove-inliner (get-match-name fname))) 146 | 147 | ;;; get-macro-name: string -> string 148 | (defn get-macro-name [mname] 149 | "extract a macro name from a qualified name" 150 | (nth (re-matches #"(.*)/(.*)" mname) 2)) 151 | 152 | 153 | (defn pretty-print-single-value 154 | "returns a pretty-printed value that is not a collection" 155 | [value] 156 | ;; need to check for nil first because .getName fails otherwise 157 | (if (nil? value) "nil" 158 | (let [fname (.getName (type value))] 159 | (cond (string? value) (str "\"" value "\"") ; strings are printed in double quotes 160 | ; extract a function from the class fname (easier than from value): 161 | (= (get-type fname) "a function") (get-function-name fname) 162 | (coll? value) "(...)" 163 | :else value)))) 164 | 165 | (defn delimeters 166 | "takes a collection and returns a vector of its delimeters as a vector of two strings" 167 | [coll] 168 | (cond 169 | (vector? coll) ["[" "]"] 170 | (set? coll) ["#{" "}"] 171 | (map? coll) ["{" "}"] 172 | :else ["(" ")"])) 173 | 174 | (defn add-commas 175 | "takes a sequence and returns a sequence of the same elements with a comma 176 | inserted after every 3rd element in every 4-element group" 177 | [sq] 178 | (loop [result [] s sq n 1] 179 | (if (empty? s) result 180 | (if (= n 4) 181 | (recur (into result ["," (first s)]) (rest s) 1) 182 | (recur (conj result (first s)) (rest s) (inc n)))))) 183 | 184 | (defn add-spaces-etc 185 | "takes a sequence s and a limit n and returns the elements of s with spaces in-between 186 | and with ... at the end if s is longer than n" 187 | [s n is-map] 188 | (let [seq-with-spaces (interpose " " (take n s)) 189 | seq-done (if is-map (add-commas seq-with-spaces) seq-with-spaces)] 190 | (if (> (count s) n) (concat seq-done '("...")) seq-done))) 191 | 192 | (defn pretty-print-nested-values 193 | "returns a vector of pretty-printed values. If it's a collection, uses the first limit 194 | number as the number of elements it prints, passes the rest of the limit numbers 195 | to the call that prints the nested elements. If no limits passed, returns (...) 196 | for a collection and teh string of a value for a single value" 197 | [value & limits] 198 | (if (or (not limits) (not (coll? value))) (pretty-print-single-value value) 199 | (let [[open close] (delimeters value) 200 | ;; a sequence of a map is a sequence of vectors, turning it into a flat sequence: 201 | flat-seq (if (map? value) (flatten (seq value)) value)] 202 | (conj (into [open] (add-spaces-etc 203 | (take (inc (first limits)) (map #(apply pretty-print-nested-values (into [%] (rest limits))) flat-seq)) 204 | (first limits) 205 | (map? value))) 206 | close)))) 207 | 208 | (defn preview-arg 209 | "returns a pretty-printed value of a preview of an arbitrary collection or value. 210 | The input consists of a value and a variable number of integers that indicate 211 | how many elements of a collection are displayed at each level. The rest of the 212 | sequence is truncated, ... is included to indicate truncated sequences." 213 | [& params] 214 | (let [pretty-val (apply pretty-print-nested-values params)] 215 | (if (coll? pretty-val) (cs/join (flatten pretty-val)) (str pretty-val)))) 216 | 217 | 218 | ;;; arg-str: number -> string 219 | (defn arg-str [n] 220 | (let [abs (fn [m] (if (> 0 m) (- m) m)) 221 | n1 (mod (abs n) 100) 222 | n2 (mod (abs n) 10)] 223 | (case n 224 | 1 "first argument" 225 | 2 "second argument" 226 | 3 "third argument" 227 | 4 "fourth argument" 228 | 5 "fifth argument" 229 | (cond 230 | (or (= 11 n1) (= 12 n1) (= 13 n1)) (str n "th argument") 231 | (= 1 n2) (str n "st argument") 232 | (= 2 n2) (str n "nd argument") 233 | (= 3 n2) (str n "rd argument") 234 | :else (str n "th argument")) 235 | ))) 236 | 237 | (defn number-word [n] 238 | (case n 239 | "0" "zero" 240 | "1" "one" 241 | "2" "two" 242 | "3" "three" 243 | "4" "four" 244 | "5" "five" 245 | "6" "six" 246 | "7" "seven" 247 | "8" "eight" 248 | "9" "nine" 249 | n)) 250 | 251 | ;;; process-asserts-obj: string or nil -> string 252 | (defn process-asserts-obj ; where the global atom (seen-failed-asserts) is used 253 | "Returns a msg-info-obj generated for an assert failure based on the 254 | global seen-failed-asserts hashmap, clears the hashmap" 255 | [n] 256 | ;; and perhaps need manual error handling, in case the seen-object is empty 257 | (let [t (:check @seen-failed-asserts) 258 | cl (:class @seen-failed-asserts) 259 | c-type (if cl (get-type (.getName cl)) "nil") 260 | fname (:fname @seen-failed-asserts) 261 | v (:value @seen-failed-asserts) 262 | v-print (preview-arg v 10 4) 263 | arg-num (if (:only-arg @seen-failed-asserts) "argument" (arg-str (if n (Integer. n) (:arg-num @seen-failed-asserts))))] 264 | (empty-seen) ; empty the seen-failed-asserts hashmap 265 | (if (not (= "nil" v-print)) 266 | (make-msg-info-hashes 267 | "In function " fname :arg ", the " arg-num " " v-print :arg 268 | " must be " t :type " but is " c-type :type ".") 269 | (make-msg-info-hashes 270 | "In function " fname :arg ", the " arg-num 271 | " must be " t :type " but is " "nil" :arg ".")))) 272 | 273 | (defn process-assert-obj-with-extra-arg 274 | "Returns a msg-info-obj generated for an assert failure based on the 275 | global seen-failed-asserts hashmap, clears the hashmap, adds 276 | an extra argument at the end of the hash map" 277 | [n addition] 278 | (add-to-msg-info (process-asserts-obj n) addition)) 279 | 280 | (defn get-compile-error-location 281 | "takes a message of a compiler error and returns 282 | the location part that matches after 'compiling', 283 | as a hashmap. Returns an empty hashmap (no keys) 284 | when there is no match" 285 | [m] 286 | (zipmap [:file :line :char] (rest (rest (re-matches #"(.*), compiling:\((.+):(.+):(.+)\)" m))))) 287 | 288 | ;; do we want to move this to corefns? 289 | (def known-args-number {:map "at least two", :count "one", :conj "at least two", :rand-nth "one", 290 | :into "two", :cons "two", :nth "two or three", :drop "two", 291 | :take "two", :filter "two", :reduce "two or three", :mapcat "at least one", 292 | :reverse "one", :sort "one or two", :sort-by "two or three", :ffirst "one", 293 | :map-indexed "two", :for "two", :pmap "two or more", :reductions "two or three", 294 | :second "one", :last "one", :rest "one", :next "one", 295 | :nfirst "one", :fnext "one", :nnext "one", :nthnext "two", 296 | :some "two", :realized? "one", :index "two", :contains? "two", 297 | :first "one", :empty? "one", :join "one or two", :string? "one", 298 | :- "at least one", :rem "two", :mod "two", :inc "one", 299 | :dec "one", :max "one or more", :min "one or more", :rand "zero or one", 300 | :rand-int "one", :odd? "one", :even? "one", :assoc "at least three", 301 | :dissoc "at least one", 302 | }) 303 | 304 | (defn lookup-arity 305 | "returns expected arity (as a string) for a function if we know it, nil otherwise" 306 | [f] 307 | ((keyword f) known-args-number)) 308 | -------------------------------------------------------------------------------- /src/errors/error_hints.clj: -------------------------------------------------------------------------------- 1 | (ns errors.error_hints) 2 | 3 | (def hints 4 | {#{:class-cast-exception} "Attempted to use a , but a was expected. 5 | The error happens when a function's argument is not of the type for which the function is defined. 6 | For instance, (+ 1 \"2\") results in this error because \"2\" is not a number, but a string. 7 | Likewise, (< 1 :two) would cause this error because :two is a keyword, and not a number. " 8 | ;This error may happen if you were using a variable and had a wrong value in the variable 9 | ; or if you have switched the order of arguments in a function. 10 | 11 | ; Error example for (+ 1 :two):\n 12 | ; \"The + function on line 7 was expecting a number but was given a keyword.\"\n 13 | ; Error example for(< 8 \"12\"):\n 14 | ; \"The < function on line 52 was expecting a number but was given a string.\"\n 15 | ; Error example for (num \"ten\"):\n 16 | ; \"The num function on line 42 was expecting a number but was given a string.\"" 17 | #{:illegal-argument-cannot-convert-type} "The ___ function on line ___ was expecting ___ but was given ___. 18 | Error example for (cons 1 2): 19 | \"The cons function on line 4 was expecting a sequence but was given a number.\" 20 | Error example for (into {} [1 2 3]): 21 | \"The into function on line 7 was expecting a vector of vectors but was given a vector.\"" 22 | #{:index-out-of-bounds-index-not-provided} "Trying to look at a certain location that doesn’t exist in a specific collection. 23 | Error example for (nth [1 2 3] 5): 24 | \"Trying to look at a certain location that doesn’t exist in a specific collection.\"" 25 | #{:arity-exception-wrong-number-of-arguments} "Make sure you have the correct number of arguments. 26 | Error example for (first [2 1] [3 6]): 27 | \"Make sure you have the correct number of arguments.\" 28 | Error example for (conj 2): 29 | \"Make sure you have the correct number of arguments.\"" 30 | #{:compiler-exception-cannot-resolve-symbol} "____ is undefined; the compiler doesn’t know what it means. See below for possible mistakes. 31 | Cases: 32 | \t -you wanted it to be a string: 33 | \t \t If you want ___ to be a string (words), put quotes around it: 34 | \t \t \"hello\" instead of hello 35 | \t -you wanted it to be a variable 36 | \t \t If you want ___ to be a variable (name for a value), define the value: 37 | \t \t (def hello 8) instead of hello 38 | \t -you wanted it to be a keyword 39 | \t \t If you want ___ to be a keyword, put a colon in front of it: 40 | \t \t :hello instead of hello 41 | \t -you mistyped a higher-order function 42 | \t \t Look back at your code for spelling errors:(first [2 3]) instead of (firST [2 3])" 43 | 44 | ;; We might also need to include a hint for accidentally using a dot or a slash 45 | #{:compiler-exception-no-such-namespace} "1. If you are using functions from other namespaces, make sure you use 'refer' in the beginning of your file to include the namespace.\n 46 | 2. Check the spelling of namespaces you might be using, such as clojure.string\n 47 | 3. Make sure that your namespace uses dots, and the function in the namespaces is separated by a slash: clojure.string/split, 48 | where clojure.string is the namespace, and split is the function." 49 | 50 | #{:class-not-found-exception} "If you are using functions from another file, make sure you use dots for namespaces and slashes for functions, such as clojure.string/split." 51 | 52 | #{:string-index-out-of-bounds} "String positions start at zero, so there is no character at a position equal to the string length. 53 | Example: a string \"hi\" does not have a character at position 2. \n 54 | Also the string may be empty, in this case accessing any position in it gives this error." 55 | 56 | #{:compiler-exception-wrong-number-of-arguments-to-recur} "1. You are passing a wrong number of arguments to recur. Check its function or loop.\n 57 | 2. recur might be outside of the scope of its function or loop." 58 | 59 | #{:null-pointer-non-existing-object-provided} "Put hint here" 60 | } 61 | ) 62 | -------------------------------------------------------------------------------- /src/errors/errorgui.clj: -------------------------------------------------------------------------------- 1 | (ns errors.errorgui 2 | (:use [seesaw.core] 3 | [clojure.string :only [join]] 4 | [errors.messageobj])) 5 | 6 | (def error-prefix "Error: ") 7 | (def comp-error-prefix "Syntax error: ") 8 | (def trace-elems-separator "\t") 9 | (def trace-lines-separator "\n") 10 | 11 | ;; Swing must be initialized to prefer native skinning before any elements are created 12 | (invoke-now (native!)) 13 | 14 | ;; Formatting functions 15 | 16 | ;(defn format-stacktrace [e] 17 | ; Force Java to give us the preformatted stacktrace as a string 18 | ; (let [writer (java.io.StringWriter.)] 19 | ; (.printStackTrace e (java.io.PrintWriter. writer)) 20 | ; (.toString writer))) 21 | 22 | (defn trace-elem->string [trace-elem trace-elems-separator] 23 | "Takes a stack trace element from a parsed exception 24 | and converts it into a string to be displayed" 25 | ; might need to change to separate handling for 26 | ; java and coljure elements and add 27 | ; handling for anonymous functions 28 | (let [ns (:ns trace-elem) 29 | ns-not-nil (if ns ns (:class trace-elem)) 30 | fn (:fn trace-elem) 31 | fn-or-method (if fn fn (:method trace-elem)) 32 | file (:file trace-elem) 33 | line (:line trace-elem)] 34 | (str trace-elems-separator ns-not-nil "/" fn-or-method " (" file " line " line ")"))) 35 | 36 | (defn trace->string [trace-elems trace-elems-separator trace-lines-separator] 37 | "Takes a stack trace from a parsed exception 38 | and converts it into a string to be displayed" 39 | (join trace-lines-separator (map #(trace-elem->string % trace-elems-separator) trace-elems))) 40 | 41 | (defn- display-msg-object! [display-msg msg-area] 42 | "add text and styles from msg-obj to msg-area" 43 | (doall (map #(style-text! msg-area 44 | (:stylekey %) 45 | (:start %) 46 | (:length %)) 47 | display-msg))) 48 | 49 | (defn- make-message-with-filtered-trace [exc-obj] 50 | "returns a display-msg that includes the filtered stacktrace" 51 | (make-display-msg (concat (make-msg-info-hashes (if (:compiler? exc-obj) comp-error-prefix error-prefix) :err) 52 | (:msg-info-obj exc-obj) 53 | (make-msg-info-hashes trace-lines-separator) ; to separate the message from the stacktrace 54 | (make-msg-info-hashes (trace->string (:filtered-stacktrace exc-obj) trace-elems-separator trace-lines-separator) 55 | :stack)))) 56 | 57 | ;; Graphics 58 | ;; msg-obj will contain parts and styles and lengths 59 | (defn display-error [exc-obj] 60 | "Takes an exception object and displays it in a swing panel" 61 | ;; extracting elements of exception-object and 62 | ;; forming a message object with the filtered stack trace to be displayed 63 | (let [trace (:stacktrace exc-obj) 64 | msg-filtered-trace (make-message-with-filtered-trace exc-obj)] 65 | (try 66 | (let ;; styles for formatting various portions of a message 67 | [styles [[:arg :font "monospace" :bold true] [:loc :font "monospace" :bold true] 68 | [:call :font "monospace" :bold true] [:reg] [:stack] [:err] [:type] [:causes]] 69 | 70 | ;; forming graphical elements of the swing panel 71 | errormsg (styled-text :wrap-lines? true :text (str (get-all-text msg-filtered-trace)) 72 | :styles styles) 73 | stacktrace (text :multi-line? true :editable? false :rows 16 :text 74 | (trace->string trace trace-elems-separator trace-lines-separator)) 75 | hints (text :multi-line? true :editable? false :rows 16 :text 76 | (:hints exc-obj)) 77 | d (dialog :title "Clojure Error", 78 | :content (tabbed-panel :placement :bottom 79 | :overflow :scroll 80 | :tabs [{:title "Error" 81 | :tip "The simplified error message" 82 | :content (do (display-msg-object! msg-filtered-trace errormsg) 83 | (scrollable errormsg))} 84 | {:title "Stacktrace" 85 | :tip "The full Java stacktrace of the error" 86 | :content (scrollable stacktrace)} 87 | ; {:title "Common sources of error" 88 | ; :tip "Reasons why this error may have happened" 89 | ; :content (scrollable hints)} 90 | ]))] 91 | (invoke-now 92 | (scroll! errormsg :to :top) ;; Scrollboxes default to being scrolled to the bottom - not what we want 93 | (scroll! stacktrace :to :top) 94 | (.setAlwaysOnTop d true) ;; to make errors pop up on top of other programs 95 | ;; a mouse anywhere in the window resets it's always-on-top to false 96 | (listen d :mouse-entered (fn [e] (.setAlwaysOnTop d false))) 97 | (-> d pack! show!))) 98 | (catch java.lang.reflect.InvocationTargetException e 99 | (if (instance? java.awt.HeadlessException (.getCause e)) 100 | ; If there is no GUI available on Windows, this throws an InvocationTargetException 101 | ; wrapping a HeadlessException - print the error instead of showing a window. 102 | (println (get-all-text msg-filtered-trace)) 103 | ; And if the error does not originate from a HeadlessException, throw it again. 104 | (throw e))) 105 | (catch java.awt.HeadlessException e 106 | ; If there is no GUI available on Linux, it simply throws a HeadlessException - print the erorr. 107 | (println (get-all-text msg-filtered-trace)))))) 108 | -------------------------------------------------------------------------------- /src/errors/exceptionobj.clj: -------------------------------------------------------------------------------- 1 | (ns errors.exceptionobj 2 | (:require [clj-stacktrace.core :as stacktrace]) 3 | (:use [errors.messageobj])) 4 | 5 | ;; Not currently used anywhere 6 | (defn exception->exceptionobj 7 | "" 8 | [e] 9 | (let [e-class nil 10 | msg-obj nil 11 | st nil 12 | fst nil 13 | hints nil] 14 | {:exception-class e-class 15 | :msg-info-obj msg-obj ;; without the stack trace 16 | :stacktrace st 17 | :filtered-stacktrace fst 18 | :hints hints})) 19 | -------------------------------------------------------------------------------- /src/errors/messageobj.clj: -------------------------------------------------------------------------------- 1 | (ns errors.messageobj) 2 | ;(:refer corefn/core :only [add-fisrt add-last])) 3 | ;; Functions related to a message object. msg-info-obj 4 | ;; is a vector of parts of a message (in order). Each 5 | ;; part is a hash-map that contains the message text :msg, 6 | ;; the formatting id (e.g. :reg), the length of the text 7 | ;; :length 8 | ;; A msg-info-obj doesn't have :start 9 | 10 | (defn make-msg-info-hash 11 | "creates a hash-map for a msg-info-obj out of a msg and style, with the form {:msg message :stylekey style :length n}" 12 | ([msg style] (let [m (str msg)] 13 | {:msg m :stylekey style :length (count m)})) 14 | ([msg] (let [m (str msg)] 15 | {:msg m :stylekey :reg :length (count m)}))) 16 | 17 | (defn- make-msg-info-hashes-helper [messages result] 18 | (if (empty? messages) result 19 | (let [next (second messages)] 20 | (if (keyword? next) (recur (rest (rest messages)) 21 | (conj result (make-msg-info-hash (first messages) next))) 22 | (recur (rest messages) 23 | (conj result (make-msg-info-hash (first messages)))))))) 24 | 25 | (defn make-msg-info-hashes [& args] 26 | "creates a vector of hash-maps out of a vector that are strings, possibly followed by optional keywords" 27 | (make-msg-info-hashes-helper args [])) 28 | 29 | 30 | (defn add-to-msg-info 31 | "adds an addition (converted to a string) to the end of an existing msg-info-obj, with an optional style keyword" 32 | ([old-msg-info addition style] (conj old-msg-info (make-msg-info-hash addition style))) 33 | ([old-msg-info addition] (conj old-msg-info (make-msg-info-hash addition)))) 34 | 35 | 36 | ;(defn make-msg-info-hashes [messages] 37 | ; "creates a vector of hash-maps out of a vector of vectors of msg + optional style" 38 | ; ;; apply is needed since messages contains vectors of 1 or 2 elements 39 | ; (map #(apply make-msg-info-hash %) messages)) 40 | 41 | (defn make-display-msg [msg-info-obj] ; msg-info-obj is a vector of hash-maps 42 | "fills in the starting points of objects in the hash maps, in the context of the output from make-msg-info-hashes " 43 | (loop [hashes msg-info-obj start 0 res []] 44 | (if (empty? hashes) res 45 | (recur (rest hashes) 46 | (+ start (:length (first hashes))) 47 | (conj res (assoc (first hashes) :start start)))))) 48 | 49 | (defn get-all-text [msg-obj] 50 | "concatenate all text from a message object into a string" 51 | ;(println (str "MESSAGE in get-all-text" msg-obj)) 52 | (reduce #(str %1 (:msg %2)) "" msg-obj)) 53 | 54 | (defn make-mock-preobj [matches] 55 | "creates a test msg-info-obj. Used for testing so that things don't break" 56 | (make-msg-info-hashes "This is a " "test." :arg)) 57 | -------------------------------------------------------------------------------- /src/errors/prettify_exception.clj: -------------------------------------------------------------------------------- 1 | (ns errors.prettify_exception 2 | (:require [clj-stacktrace.core :as stacktrace] 3 | [expectations :refer :all] 4 | [clojure.string :as str] 5 | [errors.error_dictionary :refer :all] 6 | [errors.error_hints :refer :all]) 7 | (:use [errors.dictionaries] 8 | [errors.messageobj] 9 | [errors.errorgui] 10 | [errors.stacktraces] 11 | [seesaw.core])) 12 | 13 | 14 | ;; Main error processing file. Standard errors are processed by `standard` function, and 15 | ;; modified errors are processed by `prettify-exception` function. 16 | 17 | 18 | (defn first-match 19 | [e-class message] 20 | (first (filter #(and (= (:class %) e-class) (re-matches (:match %) message)) 21 | error-dictionary))) 22 | 23 | (defn fn-name 24 | "Takes a function object and returns a symbol that corresponds to the result of 25 | the lookup of its name. 26 | If no name is found, a symbol 'anonymous function' (non-conformant) 27 | is returned. 28 | Handles spec-checking functions differently since they are looked up in corefns-map 29 | by full name. 30 | Warning: 'anonymous function' symbol is non-conformant" 31 | [f] 32 | (let [f-str (str f)] 33 | (if (re-matches #"clojure\.spec\.test\$spec_checking_fn(.*)" f-str) 34 | (symbol (get-function-name f-str)) 35 | (symbol (get-function-name (.getName (type f))))))) 36 | 37 | (defn is-function? 38 | "Uses our dictionary to check if a value should be printed as a function" 39 | [v] 40 | ;; checking for nil first: 41 | (and v (= (get-type (.getName (type v))) "a function"))) 42 | 43 | 44 | (defn single-val-str 45 | "Takes a single (non-collection) value and returns its string represntation. 46 | Returns a string 'nil' for nil, encloses strings into double quotes, 47 | performs a lookup for function names, returns 'anonymous function' for 48 | anonymous functions" 49 | [v] 50 | (cond 51 | (nil? v) "nil" 52 | (string? v) (str "\"" v "\"") 53 | (is-function? v) (fn-name v) 54 | :else (str v))) 55 | 56 | (expect "nil" (single-val-str nil)) 57 | (expect "\"hi\"" (single-val-str "hi")) 58 | (expect ":hi" (single-val-str :hi)) 59 | ;; testing for function names requires namespaces magic, so not doing it here right now 60 | 61 | (defn lookup-fns 62 | "Recursively replace internal Clojure function names with user-readable ones 63 | in the given value" 64 | [v] 65 | (cond 66 | (not (coll? v)) (if (is-function? v) (fn-name v) v) 67 | (vector? v) (into [] (map lookup-fns v)) 68 | (seq? v) (into '() (reverse (map lookup-fns v))) 69 | (set? v) (into #{} (map lookup-fns v)) 70 | (map? v) (reduce #(apply assoc %1 %2) {} (map lookup-fns v));; map has key/val pairs 71 | :else v)) 72 | 73 | (defn val-str 74 | "If v is a not a collection, returns the result of apply single-val-str to it. 75 | Otherwise returns the same collection, but with functions replaced by their 76 | names, recursively throughout the collection." 77 | [v] 78 | (if-not (coll? v) (single-val-str v) (lookup-fns v))) 79 | 80 | (defn- message-arity 81 | "Gives the arity part of the message when there is an arity error detected by spec" 82 | ;; currently doesn't use the reason, for consistency with the wording of non-spec arity errors 83 | ;; the reasons may be "Insufficient input" and "Extra input" (as strings) 84 | [reason args fname] 85 | (let [arg-args (if (= 1 (count args)) " argument" " arguments") 86 | arity (lookup-arity fname)] 87 | (make-msg-info-hashes "You cannot pass " (number-word (str (count args))) arg-args " to a function " 88 | fname :arg (if arity (str ", need " arity) "")))) 89 | 90 | (defn- function-call-string 91 | "Gives the function call part of the message for spec error messages" 92 | [args fname] 93 | (let [all-args-str (if args (str (val-str args)) "") 94 | call-str (str "(" fname " " (if args (subs all-args-str 1) ")"))] 95 | (make-msg-info-hashes ",\n" "in the function call " call-str :call))) 96 | 97 | (defn- type-from-failed-pred 98 | "Returns a type name from the name of a failed predicate" 99 | [pred-str] 100 | (cond (= pred-str "seqable?") "a sequence" 101 | (= pred-str "ifn?") "a function" 102 | (= pred-str "map?") "a hashmap" 103 | (= pred-str "integer?") "an integer number" 104 | :else (str "a " (subs pred-str 0 (dec (count pred-str)))))) 105 | 106 | (defn- or-str 107 | "Takes a vector of predicates, returns a string of their names separated by 'or'" 108 | [pred-strs] 109 | (apply str (interpose " or " (filter #(not (str/starts-with? % "a length")) ;; to weed out the args-length mismatches 110 | (distinct (map type-from-failed-pred pred-strs)))))) 111 | 112 | (defn- messages-types 113 | "Gives the part of the message for spec conditions failure" 114 | [problems value arg-num n] 115 | (let [pred-str (if (map? problems) (str (:pred problems)) (map #(str (:pred %)) problems)) ;; it's a map when it's a single predcate 116 | pred-type (if (map? problems) (type-from-failed-pred pred-str) (or-str pred-str)) 117 | value-str (val-str value) 118 | value-type (get-type-with-nil value) 119 | arg-num-str (if arg-num (if (= n 1) "argument" (arg-str (inc (first arg-num)))) "")] 120 | (if (nil? value) 121 | (make-msg-info-hashes (str ", the " arg-num-str) " must be " pred-type :type " but is " value-type :arg) 122 | (make-msg-info-hashes (str ", the " arg-num-str " ") value-str :arg " must be " pred-type :type " but is " value-type :type)))) 123 | 124 | (defn- get-predicates 125 | "If there is only one non-nil predicate in data, the hash map for that predicate 126 | is returned. If there are several non-nil predicates, a vector of their hash maps is 127 | returned." 128 | [data] 129 | (let [predicates (:clojure.spec/problems data)] 130 | (if (= 1 (count predicates)) 131 | (first predicates) 132 | (let [non-nils (filter #(not= "nil?" (str (:pred %))) predicates)] 133 | (if (= (count non-nils) 1) (first non-nils) non-nils))))) 134 | 135 | (defn- arity-error? 136 | "Returns true if all predicates have arity errors and false otherwise. 137 | Assumes that spec predicates for non-matching number of arguments start with 'length'" 138 | [problems] 139 | (every? #(str/starts-with? (str (:pred %)) "length") problems)) 140 | 141 | (defn- first-non-length 142 | "Returns the first non-length predicate" 143 | [problems] 144 | (first (filter #(not (str/starts-with? (str (:pred %)) "length")) problems))) 145 | 146 | (defn msg-info-obj-with-data 147 | "Creates a message info object from an exception that contains data" 148 | [entry message data] 149 | (let [entry-info ((:make-msg-info-obj entry) (re-matches (:match entry) message)) 150 | fname (:msg (second entry-info)) 151 | problems (get-predicates data) ; returns a predicate hashmap or a vector of pred hashmaps 152 | problem (if (map? problems) problems (first-non-length problems)) ; to make it easy to extract fields 153 | args (:clojure.spec/args data) 154 | reason (:reason problem) 155 | arity? (if reason true (arity-error? (if (map? problems) [problems] problems))) 156 | value (:val problem) 157 | arg-num (:in problem)] 158 | ;(println "Data:" data) 159 | ;(println "Problem" problem) 160 | (if arity? 161 | (into (message-arity reason args fname) (function-call-string args fname)) 162 | (into entry-info (into (messages-types problems value arg-num (count args)) (function-call-string args fname)))))) 163 | 164 | (defn msg-from-matched-entry [entry message data] 165 | "Creates a message info object from an exception and its data, if exists" 166 | (cond 167 | (and data entry) (msg-info-obj-with-data entry message data) 168 | entry ((:make-msg-info-obj entry) (re-matches (:match entry) message)) 169 | :else (make-msg-info-hashes message))) 170 | 171 | (defn hints-for-matched-entry 172 | "Returns all hints found for an exception key, as one string." 173 | [entry-key] 174 | (apply str (vals (filter #((first %) entry-key) hints)))) 175 | 176 | 177 | (defn get-location-info 178 | "returns the location at the top-most occurrence of student code, 179 | assuming that the stack trace has already been filtered" 180 | [filtered-trace] 181 | (let [top-student-line (first (filter is-student-code? filtered-trace))] 182 | {:file (:file top-student-line), :line (:line top-student-line), :fn (:fn top-student-line)})) 183 | 184 | (expect 185 | {:file "student.clj", :line 154, :fn "my-fn"} 186 | (get-location-info (filter-stacktrace 187 | [{:file "AFn.java", :line 429, :java true, :class "clojure.lang.AFn", :method "throwArity"} 188 | {:file "AFn.java", :line 44, :java true, :class "clojure.lang.AFn", :method "invoke"} 189 | {:file "Compiler.java", :line 6792, :java true, :class "clojure.lang.Compiler", :method "eval"} 190 | {:file "Compiler.java", :line 6755, :java true, :class "clojure.lang.Compiler", :method "eval"} 191 | {:file "core.clj", :line 3079, :clojure true, :ns "clojure.core", :fn "eval", :anon-fn false} 192 | {:file "student.clj", :line 154, :clojure true, :ns "intro.student", :fn "my-fn", :anon-fn false} 193 | {:file "student_test.clj", :line 153, :clojure true, :ns "intro.student-test", :fn "expect1132213279", :anon-fn true} 194 | {:file "student_test.clj", :line 150, :clojure true, :ns "intro.student-test", :fn "expect1132213279", :anon-fn false} 195 | {:file "expectations.clj", :line 229, :clojure true, :ns "expectations", :fn "test-var", :anon-fn true} 196 | {:file "expectations.clj", :line 225, :clojure true, :ns "expectations", :fn "test-var", :anon-fn false} 197 | {:file "expectations.clj", :line 263, :clojure true, :ns "expectations", :fn "test-vars", :anon-fn true} 198 | {:file "expectations.clj", :line 243, :clojure true, :ns "expectations", :fn "create-context", :anon-fn false} 199 | {:file "expectations.clj", :line 263, :clojure true, :ns "expectations", :fn "test-vars", :anon-fn false} 200 | {:file "expectations.clj", :line 308, :clojure true, :ns "expectations", :fn "run-tests", :anon-fn false} 201 | {:file "expectations.clj", :line 314, :clojure true, :ns "expectations", :fn "run-all-tests", :anon-fn false} 202 | {:file "expectations.clj", :line 571, :clojure true, :ns "expectations", :fn "eval2603", :anon-fn true} 203 | {:file nil, :line nil, :java true, :class "expectations.proxy$java.lang.Thread$ff19274a", :method "run"}]))) 204 | 205 | (defn get-cause-if-needed 206 | "returns the cause of a compilation exception in cases when we need 207 | to process the cause, not the exception itself" 208 | [e] 209 | (let [cause (.getCause e)] 210 | (if (= (class cause) clojure.lang.Compiler$CompilerException) 211 | ;recursively look for non-compiler exception cause 212 | (get-cause-if-needed cause) 213 | (if (and (= (class e) clojure.lang.Compiler$CompilerException) 214 | cause) ; has a non-nil cause 215 | cause e)))) 216 | 217 | (defn compiler-error? 218 | "an ad-hoc method to determine if an exception is really a compilation error: 219 | it's a compilation error with no cause or a generic cause." 220 | [e] 221 | (let [cause (.getCause e)] 222 | (and (= (class e) clojure.lang.Compiler$CompilerException) 223 | (or (nil? cause) (= (class cause) java.lang.RuntimeException))))) 224 | 225 | ;######################################### 226 | ;############ Location format ########### 227 | ;######################################### 228 | 229 | (defn line-number-format 230 | "Takes a line number and a character poistion and returns a string 231 | of how they are reported in an error message" 232 | [line ch] 233 | (str " on, or before, line " line)) 234 | 235 | (defn location-info 236 | "Takes the location hashmap (possibly empty) and returns a message info 237 | object to be merged with the rest of the message" 238 | [location] 239 | (if (empty? location) "" 240 | (let [file (:file location) 241 | line (:line location) 242 | character (:char location) 243 | fn-name (:fn location) 244 | ;character-msg (if character (make-msg-info-hashes " at character " character :loc) nil) 245 | ;character-msg nil ; Removed character info since it seems to confuse people 246 | fn-msg (if fn-name (make-msg-info-hashes " in function " fn-name :loc) nil)] 247 | (reduce into [(make-msg-info-hashes "\nFound in file " (clojure.string/replace file #".*\/" "") :loc (line-number-format line character) :loc) 248 | ;character-msg 249 | fn-msg 250 | (make-msg-info-hashes ".")])))) 251 | 252 | (defn location-from-data 253 | "Take exception data from a spec condition failure, 254 | return the location hashmap if not the caller is core.clj and 255 | the val-scope is clojure.core/apply" 256 | [data] 257 | (let [caller-map (:clojure.spec.test/caller data)] 258 | (when-not 259 | (and (= (:file caller-map) "core.clj") 260 | (= (:var-scope caller-map) (symbol "clojure.core/apply"))) 261 | (select-keys caller-map [:file :line])))) 262 | 263 | (defn get-exc-message 264 | "returns the message in an exception or an empty string if the message is nil" 265 | [e] 266 | (let [m (.getMessage e)] 267 | (if m m ""))) 268 | 269 | (defn- cut-stack-trace 270 | [stacktrace limit] 271 | (if (> (count stacktrace) limit) (conj (vec (take limit stacktrace)) {:file "" :line nil :clojure true :ns "and more..."}) stacktrace)) 272 | 273 | ;; All together: 274 | (defn prettify-exception [ex] 275 | (let [compiler? (compiler-error? ex) 276 | e (get-cause-if-needed ex) 277 | ;; replacing clojure.lang.LispReader$ReaderException by RuntimeException 278 | ;; to avoid code duplication in error handling since their errors 279 | ;; overlap. 280 | e-class (if (= (class e) clojure.lang.LispReader$ReaderException) RuntimeException (class e)) 281 | message (get-exc-message e) ; converting an empty message from nil to "" 282 | entry (first-match e-class message) ; We use error dictionary here in first-match 283 | data (ex-data e) 284 | exc (stacktrace/parse-exception e) 285 | stacktrace (:trace-elems exc) 286 | filtered-trace (filter-stacktrace stacktrace) 287 | spec-location (when data (location-from-data data)) 288 | comp-location (if (empty? spec-location) (get-compile-error-location (get-exc-message ex)) spec-location) 289 | location (if (empty? comp-location) (get-location-info filtered-trace) comp-location) 290 | msg-info-obj (into (msg-from-matched-entry entry message data) (location-info location)) 291 | hint-message (hints-for-matched-entry (:key entry))] 292 | ;; (println ex) 293 | ;; (println spec-location) 294 | ;; (println location) 295 | ;; create an exception object 296 | {:exception-class e-class 297 | :compiler? compiler? 298 | :msg-info-obj msg-info-obj 299 | :stacktrace stacktrace 300 | :filtered-stacktrace filtered-trace 301 | :hints hint-message})) 302 | 303 | (defn standard 304 | "returns a non-transformed error message with the non-filtered stack trace 305 | as an exception object" 306 | [ex] 307 | ;; create an exception object 308 | (let [e (get-cause-if-needed ex) 309 | ;; replacing clojure.lang.LispReader$ReaderException by RuntimeException 310 | ;; to avoid code duplication in error handling since their errors 311 | ;; overlap. 312 | e-class (if (= (class e) clojure.lang.LispReader$ReaderException) RuntimeException (class e)) 313 | message (.getMessage e) 314 | exc (stacktrace/parse-exception e) 315 | stacktrace (:trace-elems exc)] 316 | {:exception-class e-class 317 | :compiler? false 318 | :msg-info-obj (make-msg-info-hashes (str (.getName e-class)) ": " (if (> (count message) 40) (str "\n\t" message) message)) 319 | :stacktrace stacktrace ;(cut-stack-trace stacktrace 45) 320 | :filtered-stacktrace (cut-stack-trace stacktrace 40) 321 | :hints ""})) 322 | 323 | (defn trace-hashmap-to-StackTraceElement 324 | "Converts a clojure stacktrace element (hashmap) to a java StackTraceElement" 325 | [trace-hashmap] 326 | (let [declaringClass (if-let [class-name (:class trace-hashmap)] 327 | class-name 328 | (:ns trace-hashmap)) 329 | methodName (if-let [method (:method trace-hashmap)] 330 | method 331 | (:fn trace-hashmap)) 332 | fileName (:file trace-hashmap) 333 | lineNumber (:line trace-hashmap)] 334 | (new StackTraceElement declaringClass methodName fileName lineNumber))) 335 | 336 | (defn exception-obj-to-java-Throwable 337 | "Converts an exception-obj hashmap into the correct subtype of Java Throwable" 338 | [exception-obj] 339 | (let [e-class (:exception-class exception-obj) 340 | message (get-all-text (:msg-info-obj exception-obj)) 341 | java-Throwable (eval `(new ~e-class ~message)) 342 | stack-trace-element-sequence (map trace-hashmap-to-StackTraceElement (:filtered-stacktrace exception-obj)); "for each thing in :filtered-stacktrace, turn it into StackTraceElement and put it in the array" 343 | stack-trace-element-array (into-array stack-trace-element-sequence) 344 | ] 345 | (do (.setStackTrace java-Throwable stack-trace-element-array) 346 | java-Throwable))) 347 | -------------------------------------------------------------------------------- /src/errors/stacktraces.clj: -------------------------------------------------------------------------------- 1 | (ns errors.stacktraces 2 | (:require [clj-stacktrace.core :as stacktrace] 3 | [expectations :refer :all] 4 | [errors.error_dictionary :refer :all]) 5 | (:use [errors.dictionaries] 6 | [errors.messageobj])) 7 | 8 | ;; namespaces to ignore: 9 | 10 | ;; regular expressions for namespaces to be ignored. Any namespace equal to 11 | ;; or contaning these regexps would be ignored 12 | (def ignored-namespaces ["clojure.main" "clojure.lang" "java" "clojure.tools" "clojure.spec" "user" "autoexpect.runner" "expectations" "clojure.core.protocols"]) 13 | 14 | (defn- replace-dots [strings] 15 | (map #(clojure.string/replace % #"\." "\\\\.") strings)) 16 | 17 | 18 | (def namespaces-to-ignore (replace-dots ignored-namespaces)) 19 | 20 | (expect ["clojure\\.main" "clojure\\.lang" "java" "clojure\\.tools"] (replace-dots ["clojure.main" "clojure.lang" "java" "clojure.tools"])) 21 | 22 | (defn- surround-by-parens [strings] 23 | (map #(str "(" % ")") strings)) 24 | 25 | (expect ["(aaa)" "(bbb)"] (surround-by-parens ["aaa" "bbb"])) 26 | 27 | (defn- add-postfix [strings postfix] 28 | (map #(str % postfix) strings)) 29 | 30 | (expect ["aaa((\\.|/)?(.*))" "bbb((\\.|/)?(.*))"] (add-postfix ["aaa" "bbb"] "((\\.|/)?(.*))")) 31 | 32 | (defn- add-symbol-except-last [strings] 33 | (let [or-sym "|"] 34 | (conj (vec (add-postfix (butlast strings) or-sym)) (last strings)))) 35 | 36 | (expect ["aaa|" "bbb|" "ccc"] (add-symbol-except-last ["aaa" "bbb" "ccc"])) 37 | 38 | 39 | (defn- make-pattern-string [to-ignore] 40 | (let [dot-or-slash-or-nothing "((\\.|/)?(.*))"] 41 | (apply str (add-symbol-except-last (surround-by-parens (add-postfix to-ignore dot-or-slash-or-nothing)))))) 42 | 43 | 44 | (expect "(aaa((\\.|/)?(.*)))|(bbb((\\.|/)?(.*)))" 45 | (make-pattern-string ["aaa" "bbb"])) 46 | 47 | (def ns-pattern 48 | (re-pattern (make-pattern-string namespaces-to-ignore))) 49 | 50 | 51 | ;; first is needed in tests when multiple matches are returned 52 | (expect "clojure.main" (first (re-matches ns-pattern "clojure.main"))) 53 | (expect "clojure.main" (re-matches (re-pattern "clojure\\.main") "clojure.main")) 54 | (expect "clojure.main" (first (re-matches (re-pattern "clojure\\.main((\\.|/)?(.*))") "clojure.main"))) 55 | 56 | ;; specify namespaces and function names or patterns 57 | (def ignore-functions {:clojure.core [#"load.*" "require" "alter-var-root" "ex-info"]}) 58 | 59 | ;; these functions are probably not needed for beginners, but might be needed at 60 | ;; more advanced levels 61 | (def ignore-utils-functions {:clojure.core ["print-sequential" "pr-on" "pr"] :intro.test [#"eval.*"]}) 62 | 63 | (defn- ignore-function? [str-or-regex fname] 64 | (if (string? str-or-regex) (= str-or-regex fname) 65 | (re-matches str-or-regex fname))) 66 | 67 | (expect true (ignore-function? "require" "require")) 68 | (expect false (ignore-function? "require" "require5")) 69 | (expect "load" (ignore-function? #"load" "load")) 70 | (expect "load5" (ignore-function? #"load.*" "load5")) 71 | 72 | 73 | ;; this thing really needs refactoring 74 | (defn- ignored-function? [nspace fname] 75 | (let [key-ns (keyword nspace) 76 | ;; There should be only one match for filter 77 | names (key-ns (merge-with into ignore-functions ignore-utils-functions))] 78 | ;names (key-ns functions-for-namespace)] 79 | (if (nil? names) false (not (empty? (filter #(ignore-function? % fname) names)))))) 80 | 81 | (expect true (ignored-function? "clojure.core" "require")) 82 | (expect false (ignored-function? "clojure.lang" "require")) 83 | (expect false (ignored-function? "clojure.core" "require5")) 84 | (expect true (ignored-function? "clojure.core" "load-one")) 85 | 86 | 87 | (defn keep-stack-trace-elem 88 | "returns true if the stack trace element should be kept 89 | and false otherwise" 90 | [st-elem] 91 | (let [nspace (:ns st-elem) 92 | namespace (if nspace nspace "") ;; in case there's no :ns element 93 | fname (:fn st-elem)] 94 | (and (:clojure st-elem) (not (re-matches ns-pattern namespace)) 95 | (not (ignored-function? namespace fname))))) 96 | 97 | (defn filter-stacktrace 98 | "takes a stack trace and filters out unneeded elements" 99 | [stacktrace] 100 | ;(println stacktrace) 101 | ;(println (filter keep-stack-trace-elem stacktrace)) 102 | (filter keep-stack-trace-elem stacktrace)) 103 | 104 | (def non-student-namespaces ["clojure." "corefns"]) 105 | 106 | (def ns-pattern-non-student 107 | (re-pattern (make-pattern-string non-student-namespaces))) 108 | 109 | (defn is-student-code? 110 | "determines whether a stack trace element corresponds to student code, 111 | assuming that the element appears in the filtered stacktrace" 112 | [st-elem] 113 | (let [nspace (:ns st-elem) 114 | namespace (if nspace nspace "")] 115 | (not (re-matches ns-pattern-non-student namespace)))) 116 | 117 | (expect true (is-student-code? {:file "NO_SOURCE_FILE", :line 154, :clojure true, :ns "intro.student", :fn "eval15883"})) 118 | (expect false (is-student-code? {:file "core.clj", :line 3079, :clojure true, :ns "clojure.core", :fn "eval", :anon-fn false})) 119 | -------------------------------------------------------------------------------- /src/intro/core.clj: -------------------------------------------------------------------------------- 1 | (ns intro.core 2 | (:use [errors.prettify_exception] 3 | [seesaw.core]) 4 | (:require [expectations :refer :all] 5 | [errors.errorgui :refer :all] 6 | [intro.student :refer :all] 7 | [utilities.file_IO :refer :all] 8 | [errors.prettify_exception :refer :all] 9 | [clojure.pprint :refer :all] 10 | )) 11 | 12 | (defn -main [& args] 13 | (try 14 | ; (try 15 | (if (= (first args) "s") 16 | (print (load-file "src/intro/test_standard.clj")) 17 | (print (load-file "src/intro/test.clj"))) 18 | ;(catch Throwable e (print (str "itself:" (class e) ", cause: " (class (.getCause e)))) (throw e))) 19 | 20 | (catch Throwable e ;(print (str "itself:" (class e) ", cause: " (class (.getCause e)))) 21 | (if (= (first args) "s") 22 | (display-error (standard e)) 23 | (display-error (prettify-exception e)))))) 24 | -------------------------------------------------------------------------------- /src/intro/student.clj: -------------------------------------------------------------------------------- 1 | (ns intro.student 2 | (:require [expectations :refer :all] 3 | [corefns.corefns :refer :all] 4 | [corefns.collection_fns :refer :all])) 5 | 6 | (defn fffff [x] 7 | (map 2 x)) 8 | 9 | ;################################## 10 | ;### Testing compilation errors ### 11 | ;################################## 12 | 13 | ;(def f [x] (+ x 2)) 14 | ;(+ (* 2 3) 7 15 | 16 | ;(+ (* 2 3) 7)] 17 | 18 | ;(defn f[x] (+ x y)) 19 | 20 | ;(defn f[x y] 21 | ; (fn [x] (+ x y z))) 22 | 23 | ;(loop [x 1 y 2] 24 | ; (if (= x 0) 1 25 | ; (recur (dec x) 5))) 26 | 27 | 28 | ;############################################################# 29 | ;### 4clojure and beginner problems like students would do ### 30 | ;############################################################# 31 | 32 | ;; 4clojure Problem 44 33 | (declare prob44) 34 | 35 | (defn prob44-helper-positive [n coll] 36 | (let [result (add-last (rest coll) (first coll))] 37 | (prob44 (- n 1) result))) 38 | 39 | (defn prob44-helper-negative [n coll] 40 | (let [result (add-first (butlast coll) (last coll))] 41 | (prob44 (+ n 1) result))) 42 | 43 | (defn prob44 [n coll] 44 | (cond 45 | (= n 0) coll ;n equal to zero 46 | (> n 0) (prob44-helper-positive n coll) ;n is positive 47 | (< n 0) (prob44-helper-negative n coll))) ;n is negative 48 | 49 | ;; DrRacket Excercise 2 - exception in exceptions/DrRacket-Exercise2-ClassCast.ser 50 | ; ERROR: Attempted to use a string, but a collection was expected. 51 | ; possible hint: are you sure conj is the function you should be using? 52 | ; possible hint: conj cannot be used on strings 53 | ; possible tag :conj 54 | (defn exercise2 [str1 str2] 55 | (conj str1 str2)) 56 | 57 | ;; DrRacket Exercise 3 - exception in exception/DrRacket-Exercise3-IndexOutOfBounds.ser 58 | ; ERROR: String index out of range: 12 59 | (defn exercise3 [a-string index] 60 | (str (subs a-string 0 index) "_" (subs a-string index (+ 2 (count a-string))))) 61 | 62 | ;; 4clojure Problem 15 - exception in exceptions/4clojure-prob15-ClassCast.ser\ 63 | ; ERROR: Attempted to use a number, but a function was expected. 64 | ; possible hint: make sure you're using prefix notation 65 | (defn prob15 [num] 66 | (2 * num)) 67 | 68 | ;; 4clojure Problem 16 - exception in exceptions/4clojure-prob16-Arity.ser 69 | ; ERROR: Wrong number of arguments (3) passed to a function cons 70 | ; possible tag :cons 71 | ; possible hint: are you sure cons is the function you should be using? 72 | (defn prob16 [a-string] 73 | (cons "Hello, " a-string "!")) 74 | 75 | ;; 4clojure Problem 17 - exception in exceptions/4clojure-prob17-NullPointer.ser 76 | ; ERROR: An attempt to access a non-existing object. 77 | ; (NullPointerException) 78 | ; possible hint: make sure you have a base (termination) case 79 | ; possible hint: make sure you're not applying an operation to nil 80 | (defn prob17 [coll-of-nums] 81 | (loop [coll coll-of-nums 82 | result '()] 83 | (recur (rest coll) 84 | (conj result (+ 5 (first coll)))))) 85 | 86 | ;; 4clojure Problem 18 - exception in exceptions/4clojure-prob18-AssertionError.ser 87 | ; ERROR: in function < first argument nil must be a number but is nil 88 | ; possible hint: make sure that you have a base (termination) case 89 | (defn prob18 [coll-of-nums] 90 | (if (< (first coll-of-nums) 5) 91 | (list (first coll-of-nums))) 92 | (prob18 (rest coll-of-nums))) 93 | 94 | ;; 4clojure Problem 64 - exception in exceptions/4clojure-prob64-NullPointer.ser -- from saved exception, not from recent running 95 | ; ERROR: An attempt to access a non-existing object. 96 | ; (NullPointerException) 97 | ; possible hint: make sure you have a base (termination) case 98 | ; possible hint: make sure you're not applying an operation to nil 99 | (defn prob64 [coll-of-nums] 100 | (loop [coll coll-of-nums 101 | result 0] 102 | (recur (rest coll) 103 | (+ result (first coll))))) 104 | 105 | ;; take-while now is allowed arity 1, as of Clojure 1.7 106 | ;; 4clojure Problem 57 - exception in exceptions/4clojure-prob57-ArityException.ser 107 | ; ERROR: Wrong number of arguments (1) passed to a function take-while 108 | ; possible hint: make sure you're passing the right number of args to your functions 109 | (defn prob57 [num] 110 | (take-while (range num))) 111 | 112 | ;; 4clojure Problem 134 - exception in exceptions/4clojure-prob134-AssertionError.ser 113 | ; ERROR: in function filter first argument :a must be a function but is a keyword 114 | ; possible hint: keywords can't be used as functions in this context 115 | ;; Elena, 6/17/16: keywords are functions and should be used as such. This shouldn't be an error. 116 | ;; If we want to think of restrictions for beginners, we should be careful with how we set it up 117 | (defn prob134 [key map] 118 | (nil? (filter key map))) 119 | 120 | ;(defn prob145 [num] 121 | ; (for [x (range num)] 122 | ; (when (= 1 (rem num 4)) 123 | ; x))) 124 | 125 | ;(defn prob145 [num] 126 | ; (filter (fn [] (= 1 (rem 4 %))) (range num))) 127 | 128 | ;; 4clojure Problem 156 - exception in exceptions/4clojure-prob156-AssertionError.ser 129 | ; ERROR: in function map first argument {:a 0} must be a function but is a map 130 | ; possible hint: be sure that your arguments are of the correct types 131 | (defn prob156 [default keys] 132 | (into {} (map (into {} 133 | {(first keys) default}) 134 | keys))) 135 | 136 | ;; 4clojure Problem 20 - exception in exceptions/4clojure-prob20-AssertionError.ser 137 | ; ERROR: in function nth first argument 3 must be a sequence but is a number 138 | ; possible hint: be sure that your arguments are in the correct order 139 | (defn prob20 [coll] 140 | (nth (- (count coll) 1) coll)) 141 | 142 | ;; take now is allowed arity 1, as of Clojure 1.7 143 | ;; 4clojure Problem 21 - exception in exceptions/4clojure-prob21-ArityException.ser 144 | ; ERROR: Wrong number of arguments (1) passed to a function take 145 | ; possible hint: make sure you're using the right number of arguments. 146 | (defn prob21 [coll n] 147 | (drop (first coll) (take n))) 148 | 149 | ;; Something Emma made, trying to add numbers - exception in exceptions/add-five-IllegalArgException.ser 150 | ; ERROR: Don't know how to create a sequence from a number 151 | ; possible hint: cons is supposed to be used for collections 152 | (defn add-five [n] 153 | (cons n 5)) 154 | 155 | ;; 4clojure Problem 24 - exception in exceptions/4clojure-prob24-ClassCast.ser 156 | ; ERROR: Attempted to use a sequence, but a number was expected. 157 | ; possible hint: be sure you are using the correct argument types 158 | (defn prob24 [coll] 159 | (do 160 | (+ (first coll) (rest coll)) 161 | (prob24 (rest rest coll)))) 162 | 163 | ;; 4clojure Problem 23 - exception in exceptions/4clojure-prob23-IndexOutOfBounds.ser 164 | ; ERROR: An index in a sequence is out of bounds or invalid 165 | ; possible hint: make sure that the sequence you're using has the desired index 166 | (defn prob23 [coll] 167 | (loop [old-coll coll 168 | new-coll []] 169 | (if (empty? old-coll) 170 | new-coll 171 | (recur (rest old-coll) (cons (nth old-coll (count old-coll)) new-coll))))) 172 | 173 | ;; 4clojure Problem 27 - exception in exceptions/4clojure-prob27-ArityException.ser 174 | ; ERROR: Wrong number of arguments (0) passed to a function equal-to? 175 | ; possible hint: make sure that you're passing the correct amount of parameters 176 | (defn equal-to? [thing1 thing2] 177 | (= (first thing1) (first thing2))) 178 | 179 | (defn prob27 [thing] 180 | (loop [thing thing 181 | reverse-thing (reverse thing)] 182 | (if (empty? thing) 183 | true 184 | (if (equal-to?) 185 | (recur (rest thing) (rest reverse-thing)) 186 | false)))) 187 | 188 | ;; Dr Racket exercise 9 - exception in exceptions/DrRacket-Exercise9-ArityException.ser 189 | ; ERROR: Wrong number of arguments (1) passed to a function fn--6065 190 | ; possible hint: make sure you're arguments to your functions are correct 191 | (defn exercise9 [] 192 | (let [b1 true 193 | b2 false] 194 | (or (#(false?) b1) 195 | (#(true?) b2)))) 196 | 197 | ;; 4clojure Problem 38 - exception in exceptions/4clojure-prob38-ArityException.ser 198 | ; ERROR: Wrong number of arguments (4) passed to a function prob38 199 | ; possible hint: if you want a function to take an unlimited number of arguments, use an & in the 200 | ; parameter vector like this: [& args] 201 | (defn prob38 [args] 202 | (if (= 1 (count args)) 203 | args 204 | (prob38 (if (> (first args) (second args)) 205 | (prob38 (cons (first args) (rest (rest args)))) 206 | (prob38 (rest args)))))) 207 | 208 | (defn error-in-anonymous [] 209 | (doall 210 | (map 211 | #(+ % 2) 212 | [2 3 "hi" "bye"]))) 213 | 214 | (defn error-in-map-inc [] 215 | (doall 216 | (map 217 | inc 218 | [2 3 "hi" "bye"]))) 219 | (def not-your-usual-name 5) 220 | -------------------------------------------------------------------------------- /src/utilities/defn_generator.clj: -------------------------------------------------------------------------------- 1 | (ns utilities.defn_generator 2 | (:require [corefns.assert_handling :refer :all])) 3 | 4 | 5 | ;;the only part of this hash map we still use is :has-type 6 | ;;the other parts used to be useful when I was also doing the re-defining, and may yet be useful 7 | (def type-data { 8 | :arg {:check nil, :has-type "arg", :argument "arg", :arg-vec ["arg"]}, 9 | :coll {:check "check-if-seqable?", :has-type "seqable", :argument "coll", :arg-vec ["coll"]}, 10 | :n {:check "check-if-number?", :has-type "number", :argument "n", :arg-vec ["n"]}, 11 | :colls {:check "check-if-seqables?", :has-type "seqable", :argument "args", :arg-vec ["&" "args"]}, 12 | :str {:check "check-if-string?", :has-type "string", :argument "str", :arg-vec ["str"]}, 13 | :strs {:check "check-if-strings?", :has-type "string", :argument "strs", :arg-vec ["&" "strs"]}, 14 | :f {:check "check-if-function?", :has-type "function", :argument "f", :arg-vec ["f"]}, 15 | :fs {:check "check-if-functions?", :has-type "function", :argument "fs", :arg-vec ["&" "fs"]} 16 | :args {:check nil, :has-type "arg", :argument "args", :arg-vec ["&" "args"]}}) 17 | 18 | ;;mapping of rich hickey's argument names in doc-strings to a more consistent naming scheme 19 | ;; (def arg-type (merge 20 | ;; (zipmap [:coll :c :c1 :c2 :c3 :c4 :c5] (repeat :coll)), 21 | ;; (zipmap [:n :number :step :start :end :size] (repeat :n)), 22 | ;; (zipmap [:arg :val :argument :x :y] (repeat :arg)), 23 | ;; (zipmap [:f :function :pred] (repeat :f)), 24 | ;; (zipmap [:fs :functions :preds] (repeat :fs)), 25 | ;; (zipmap [:colls :cs] (repeat :colls)), 26 | ;; (zipmap [:string :str :s] (repeat :str)), 27 | ;; (zipmap [:strs :strings :ss] (repeat :strs)), 28 | ;; (zipmap [:more :args :vals :arguments :xs :ys] (repeat :args)))) 29 | (def arg-type (merge 30 | (zipmap [:seq] (repeat :seq)), ; added 31 | (zipmap [:map] (repeat :map-or-vector)), ;added 32 | (zipmap [:coll :c :c1 :c2 :c3 :c4 :c5] (repeat :coll)), 33 | (zipmap [:maps] (repeat :maps-or-vectors)), ;added 34 | (zipmap [:n :number :step :start :end :size] (repeat :n)), 35 | (zipmap [:arg :key :val :argument :x :y] (repeat :arg)), ; key is added 36 | (zipmap [:f :function :pred] (repeat :f)), 37 | (zipmap [:fs :functions :preds] (repeat :fs)), 38 | (zipmap [:colls :cs] (repeat :colls)), 39 | (zipmap [:string :str :s] (repeat :str)), 40 | (zipmap [:strs :strings :ss] (repeat :strs)), 41 | (zipmap [:more :args :vals :arguments :xs :ys] (repeat :args)))) 42 | 43 | ;; returns the index of the last logically false member of the array 44 | ;; coll -> number 45 | (defn last-false-index [coll] 46 | (loop [remaining coll 47 | i 1 48 | j 0] 49 | (if (empty? remaining) j (recur (rest remaining) (inc i) (if (first remaining) j i))))) 50 | 51 | ;;does the translating from the names rich hickey gave the arguments, to something consistent 52 | ;;list of lists of strings -> list of lists of keys 53 | (defn arglists->argtypes [arglists] (map (fn [x] (map #(arg-type (keyword %)) x)) arglists)) 54 | 55 | ;;returns the longest collection in a collection of collections 56 | ;;arglists -> arglist 57 | (defn last-arglist [arglists] (first (reverse (sort-by count arglists)))) 58 | 59 | ;;returns trus if an arglists has & ____ as an argument 60 | ;;arglists -> boolean 61 | (defn chompable? [arglists] (or (not-any? #(< 1 (count %)) arglists) (= "&" (name (first (rest (reverse (last-arglist arglists)))))))) 62 | 63 | ;;removes the second to last element of the longest coll in a coll of colls 64 | ;;this is the `&` symbol in our case 65 | ;;arglists -> arglists 66 | (defn remove-and [arglists] 67 | (let [sorted-arglists (reverse (sort-by count arglists)) 68 | f-arglists (first sorted-arglists) 69 | r-arglists (reverse (rest sorted-arglists))] 70 | (sort-by count (conj r-arglists (into (vec (drop-last 2 f-arglists)) (take-last 1 f-arglists)))))) 71 | 72 | ;;returns the last element of the longest coll in a coll of colls 73 | ;;arglists -> keyword 74 | (defn last-arg [arglists] (keyword (str (name (first (reverse (first (reverse (sort-by count arglists))))))))) 75 | 76 | ;;checks the :type-data of each argument, returning true if they are all the same 77 | ;;args -> boolean 78 | (defn same-type? [& args] 79 | (apply = (map #(:has-type (type-data %)) args))) 80 | 81 | ;;helper function for chomp-arglists, appends last-arg to the end of the longest coll in arglists 82 | ;;arglists -> arglists 83 | (defn append-last-arg [arglists last-arg] 84 | (conj (vec (rest (reverse (sort-by count arglists)))) (conj (vec (first (reverse (sort-by count arglists)))) last-arg))) 85 | 86 | ;;removes redundant arguments in arglists 87 | ;;arglists -> arglists 88 | (defn chomp-arglists [arglists] 89 | (let [f-arglists (arglists->argtypes (remove-and arglists)) 90 | last-arg (first (reverse (first (reverse (sort-by count f-arglists))))) 91 | ] 92 | (loop [rem-args f-arglists 93 | diffs []] 94 | (cond 95 | (empty? rem-args) (append-last-arg (vec (filter #(<= (count %) (last-false-index diffs)) f-arglists)) last-arg) 96 | :else (recur (drop-while empty? (map rest rem-args)) (into diffs (apply map same-type? (conj rem-args [last-arg])))))))) 97 | 98 | ;;runs chomp-arglist if the & is present, else just translates to our representation 99 | ;;arglists -> arglists 100 | (defn chomp-if-necessary [arglists] 101 | (if (chompable? arglists) 102 | (chomp-arglists arglists) 103 | (arglists->argtypes arglists))) 104 | 105 | ;; outputs a string of generated data for redefining functions with preconditions 106 | ;; function -> string 107 | (defn pre-re-defn [fvar] 108 | ;; (println "pre-re-defn ing: " (:name (meta fvar))) 109 | (let [fmeta (meta fvar)] 110 | (str "(re-defn #'" (:ns fmeta) "/" (:name fmeta) " " 111 | (apply str (vec (interpose " " (map vec (chomp-if-necessary (map #(map name %) (:arglists fmeta))))))) ")"))) 112 | 113 | 114 | (defn println-recur [all-vars] 115 | (when 116 | (not (empty? all-vars)) 117 | (try 118 | (println (pre-re-defn (first all-vars))) 119 | (catch java.lang.ClassCastException e)) 120 | (println-recur (rest all-vars)))) 121 | ;; (println-recur (vals (ns-publics 'clojure.core))) 122 | 123 | (defn println-recur-criminals [all-vars] 124 | (when 125 | (not (empty? all-vars)) 126 | (try 127 | (pre-re-defn (first all-vars)) 128 | (catch java.lang.ClassCastException e 129 | (println (first all-vars)))) 130 | (println-recur-criminals (rest all-vars)))) 131 | ;; (println-recur-criminals (vals (ns-publics 'clojure.core))) 132 | 133 | 134 | ;;probably depricated since spec, but below is slightly buggy code to automate the re defining of functions 135 | ;;This should be deleted after I am certain it is no longer useful. 136 | ; 137 | ;;;adds argument counts to the hashmap being passed around, this is a helper function 138 | ;;;for the old way of redefining functions 139 | ;;;coll -> coll 140 | ;(defn add-counts [coll] 141 | ; (let [cnt-key (fn [k coll] (count (filter #(= % k) coll)))] 142 | ; (loop [coll-keys (keys coll) 143 | ; coll-vals (vals coll) 144 | ; out {}] 145 | ; (if (empty? coll-keys) 146 | ; out 147 | ; (recur (rest coll-keys) (rest coll-vals) 148 | ; (assoc out (first coll-keys) (assoc (first coll-vals) :cnt-str (if (= 1 (cnt-key (first coll-keys) (keys coll))) "" (cnt-key (first coll-keys) coll-keys))))))))) 149 | ; 150 | ;;; string vector vector -> string 151 | ;(defn gen-checks [unqualified-name data] 152 | ; (reduce #(str %1 (str unqualified-name "(check-if-" (:type %2) "? \"" unqualified-name "\" " (:argument %2) (:cnt-str %2))) "" data)) 153 | ; 154 | ;;; string vector -> string 155 | ;(defn single-re-defn [fname fnamespace arg-types only] 156 | ; (let [f (symbol fname) 157 | ; unqualified-name (name f) 158 | ; qualified-name (str fnamespace "/" unqualified-name) 159 | ; arg-data (add-counts (clj->ourtypes arg-types)) 160 | ; do-apply (if (not (empty? (first arg-types))) (re-matches #".*s$" (first (reverse arg-types))) nil) 161 | ; arg-str (apply str (interpose " " (map #(str (:arg-str %) (:cnt-str %)) arg-data))) 162 | ; checks (gen-checks fname arg-data) 163 | ; ] 164 | ; ;(println "arg-vec: " arg-vec) 165 | ; (str " " (if only "" "(") "[" arg-str "]" 166 | ; (if (= (count checks) 0) "" (str "\n {:pre [" checks "]}")) 167 | ; "\n" (str " ("(if do-apply "apply " "") qualified-name " [" arg-str "])") 168 | ; (if only "" ")")))) 169 | ;; 170 | ;;; takes a function name, and a vector of vectors of arguments 171 | ;;; note: arguments DO NOT end in a question mark. 172 | ;(defn re-defn [fvar & arglists] 173 | ; (str "(defn " (:name (meta fvar)) "\n \"" (:doc (meta fvar)) "\"" 174 | ; (reduce #(str %1 "\n" (single-re-defn (:name (meta fvar)) (:ns (meta fvar)) %2 (= 1 (count arglists)))) "" arglists) ")")) 175 | 176 | 177 | -------------------------------------------------------------------------------- /test/corefns/corefns_test.clj: -------------------------------------------------------------------------------- 1 | (ns corefns.corefns_test 2 | (:require [expectations :refer :all] 3 | [corefns.corefns :refer :all] 4 | [errors.messageobj :refer :all] 5 | [errors.testing_tools :refer :all] 6 | [corefns.collection_fns :refer :all] 7 | [intro.core :refer :all])) 8 | 9 | ;############################################# 10 | ;### Testing the functionality of corefns ### 11 | ;### These tests check that we didn't mess ### 12 | ;### up core functionality by overwriting ### 13 | ;############################################# 14 | 15 | 16 | 17 | 18 | 19 | ;;; Elena, 6/22/16: Tests below this line should be moved from here 20 | 21 | ;################################################# 22 | ;### Testing if the corefns preconditions work ### 23 | ;################################################# 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /test/errors/dictionaries_test.clj: -------------------------------------------------------------------------------- 1 | (ns errors.dictionaries_test 2 | (:require [errors.dictionaries :refer :all] 3 | [expectations :refer :all])) 4 | 5 | ;######################################### 6 | ;### Tests for supplementary functions ### 7 | ;### in errors.dictionaries ### 8 | ;######################################### 9 | 10 | ;##################################### 11 | ;### Testing for get-function-name ### 12 | ;##################################### 13 | 14 | (expect "days" (get-function-name "happy$days")) 15 | (expect "days" (get-function-name "happy/days")) 16 | (expect "blahBlahBlahBlahNuthin'" (get-function-name "blahBlahBlahBlahNuthin'")) 17 | 18 | ;############################################### 19 | ;### Testing for check-if-anonymous-function ### 20 | ;############################################### 21 | 22 | (expect "anonymous-function" (check-if-anonymous-function "fn")) 23 | (expect "anonymous-function" (check-if-anonymous-function "fn_test")) 24 | (expect "anonymous-function" (check-if-anonymous-function "fn_")) 25 | (expect "random_function" (check-if-anonymous-function "random_function")) 26 | 27 | ;###################################### 28 | ;### Testing for location functions ### 29 | ;###################################### 30 | 31 | (expect {} (get-compile-error-location "")) 32 | 33 | (expect ["[" "]"] (delimeters [6 7])) 34 | (expect ["{" "}"] (delimeters (assoc {} :a 1))) 35 | (expect ["#{" "}"] (delimeters (set [2 3 2 3]))) 36 | (expect ["(" ")"] (delimeters (range))) 37 | 38 | ;; 39 | (expect "2" (preview-arg 2 10 3)) 40 | (expect "\"hi\"" (preview-arg "hi" 10 3)) 41 | (expect "[2 3]" (preview-arg [2 3] 10 3)) 42 | (expect "(2 3)" (preview-arg '(2 3) 10 3)) 43 | 44 | (expect '(2 " " 3 "," " " 4 " " 5) (add-commas '(2 " " 3 " " 4 " " 5))) 45 | 46 | (expect '(1 2 3 "," 4 5 6 7 "," 8 9 10 11) (add-commas '(1 2 3 4 5 6 7 8 9 10 11))) 47 | 48 | (expect '(1 2 3 "," 4 5 6 7 "," 8 9 10 11 "," 12 13 14) (add-commas '(1 2 3 4 5 6 7 8 9 10 11 12 13 14))) 49 | -------------------------------------------------------------------------------- /test/errors/error_dictionary_test.clj: -------------------------------------------------------------------------------- 1 | (ns errors.error_dictionary_test 2 | (:use [errors.prettify_exception :only [line-number-format]]) 3 | (:require [expectations :refer :all] 4 | [errors.messageobj :refer :all] 5 | [errors.testing_tools :refer :all] 6 | [errors.prettify_exception :refer :all] 7 | [utilities.file_IO :refer :all] 8 | )) 9 | 10 | 11 | 12 | 13 | ;################################## 14 | ;### Testing for Java Exceptions### 15 | ;################################## 16 | 17 | ;; Elena: this is a compilation error in clojure 1.7, so we can't test it like this 18 | ;; testing for :java.lang.Exception-improper-identifier 19 | ;(expect "You cannot use 7 as a variable." 20 | ; (get-all-text (run-and-catch-pretty-no-stacktrace 'intro.core '(let [x :two 7 :seven])))) 21 | 22 | 23 | 24 | 25 | 26 | ;; :compiler-exception-must-recur-to-function-or-loop 27 | -------------------------------------------------------------------------------- /test/errors/error_hints_test.clj: -------------------------------------------------------------------------------- 1 | (ns errors.error_hints_test 2 | (:require [expectations :refer :all] 3 | [corefns.corefns :refer :all] 4 | [errors.prettify_exception :refer :all] 5 | [errors.testing_tools :refer :all] 6 | [errors.error_hints :refer :all])) 7 | 8 | 9 | (expect #"(.*)Check the spelling of namespaces you might be using, such as clojure.string\n(.*)" 10 | (:hints (run-and-catch-pretty-with-stacktrace 'intro.core '(clojure/string/splt "pattern" #"/")))) 11 | 12 | (expect "If you are using functions from another file, make sure you use dots for namespaces and slashes for functions, such as clojure.string/split." 13 | (:hints (run-and-catch-pretty-with-stacktrace 'intro.core '(clojure.string.splt "pattern" #"/")))) 14 | 15 | (expect #"(.*)Make sure you have the correct number of arguments(.*)" 16 | (:hints (run-and-catch-pretty-with-stacktrace 'intro.student '(#(+ % 1) 2 3)))) 17 | 18 | ;; Currently there are no hints for this, using it as a test for empty hints. 19 | (expect "" 20 | (:hints (run-and-catch-pretty-with-stacktrace 'intro.student '(map 2 3)))) 21 | 22 | -------------------------------------------------------------------------------- /test/errors/prettify_exception_test.clj: -------------------------------------------------------------------------------- 1 | (ns errors.prettify_exception_test 2 | (:require [expectations :refer :all] 3 | [errors.messageobj :refer :all] 4 | [errors.testing_tools :refer :all] 5 | [errors.errorgui :refer :all] 6 | [clj-stacktrace.core :as stacktrace] 7 | [errors.dictionaries :refer :all] 8 | [utilities.file_IO :refer :all] 9 | [errors.prettify_exception :refer :all])) 10 | 11 | ;;; INDEX ;;; 12 | 13 | ;;1. Functions 14 | ;;2. Prebuilt Exceptions 15 | ;;3. errors.messageobj 16 | ;;4. errors.exceptionobj 17 | ;;5. errors.core, errorgui tests 18 | ;;6. Testing basics of dictionaries.clj 19 | 20 | ;#################### 21 | ;### 1. Functions ### 22 | ;#################### 23 | 24 | (defn ignore-stacktrace [trace1 trace2] true) 25 | 26 | ;############################### 27 | ;### 2. Prebuilt Exceptions #### 28 | ;############################### 29 | 30 | (def function-call-on-value (run-and-catch-raw '(4 + "pie"))) 31 | 32 | (def class-cast-exception (run-and-catch-raw '(+ 4 "pie"))) 33 | 34 | ;################################## 35 | ;### 3. errors.messageobj tests ### 36 | ;################################## 37 | 38 | ;***Testing make-msg-info-hashes*** 39 | 40 | (expect (make-msg-info-hashes) []) 41 | 42 | ;***Testing make-msg-info-hash*** 43 | 44 | (expect (make-msg-info-hash "Hi there") 45 | {:msg "Hi there" :stylekey :reg :length 8}) 46 | 47 | (expect (make-msg-info-hash "Hi there" :arg) 48 | {:msg "Hi there" :stylekey :arg :length 8}) 49 | 50 | (expect (make-msg-info-hash "Blue Jeans") 51 | {:msg "Blue Jeans", :stylekey :reg, :length 10}) 52 | 53 | (expect (make-msg-info-hash "Bootcut" :hippy) 54 | {:msg "Bootcut", :stylekey :hippy, :length 7}) 55 | 56 | ;***Testing make-msg-info-hashes*** 57 | 58 | (expect (make-msg-info-hashes "Hi there") 59 | [{:msg "Hi there" :stylekey :reg :length 8}]) 60 | 61 | (expect (make-msg-info-hashes "Hi there" :arg) 62 | [{:msg "Hi there" :stylekey :arg :length 8}]) 63 | 64 | (expect (make-msg-info-hashes "Hi there" "Hello") 65 | [{:msg "Hi there" :stylekey :reg :length 8} 66 | {:msg "Hello" :stylekey :reg :length 5}]) 67 | 68 | (expect (make-msg-info-hashes "Hi there" :arg "Hello" :blah) 69 | [{:msg "Hi there" :stylekey :arg :length 8} 70 | {:msg "Hello" :stylekey :blah :length 5}]) 71 | 72 | (expect (make-msg-info-hashes "Blue Jeans" 73 | "Khakis" :business-casual 74 | "Bootcut" :hippy 75 | "Jeggings" :casual) 76 | 77 | [{:msg "Blue Jeans", :stylekey :reg, :length 10} 78 | {:msg "Khakis", :stylekey :business-casual, :length 6} 79 | {:msg "Bootcut", :stylekey :hippy, :length 7} 80 | {:msg "Jeggings", :stylekey :casual, :length 8}]) 81 | 82 | ;***Testing make-display-msg*** 83 | 84 | (expect (make-display-msg (make-msg-info-hashes "Hi there" :arg "Hello")) 85 | [{:msg "Hi there" :stylekey :arg :length 8 :start 0} 86 | {:msg "Hello" :stylekey :reg :length 5 :start 8}]) 87 | 88 | (expect (make-display-msg [{:msg "Blue Jeans", :stylekey :reg, :length 10} 89 | {:msg "Khakis", :stylekey :business-casual, :length 6} 90 | {:msg "Bootcut", :stylekey :hippy, :length 7} 91 | {:msg "Jeggings", :stylekey :casual, :length 8}]) 92 | 93 | [{:start 0, :msg "Blue Jeans", :stylekey :reg, :length 10} 94 | {:start 10, :msg "Khakis", :stylekey :business-casual, :length 6} 95 | {:start 16, :msg "Bootcut", :stylekey :hippy, :length 7} 96 | {:start 23, :msg "Jeggings", :stylekey :casual, :length 8}]) 97 | 98 | ;***Testing get-all-text*** 99 | 100 | (expect (get-all-text (make-display-msg (make-msg-info-hashes "Hi there! " :arg "Hello"))) 101 | "Hi there! Hello") 102 | 103 | ;***Testing make-mock-preobj*** 104 | 105 | (expect (make-mock-preobj ["anything" "will be ignored"]) 106 | [{:msg "This is a " :stylekey :reg :length 10} 107 | {:msg "test." :stylekey :arg :length 5}]) 108 | 109 | 110 | ;###################################### 111 | ;### 5. errors.core, errorgui tests ### 112 | ;###################################### 113 | 114 | ;; get a pre-stored exception 115 | (def classcast-exc (import-from-file "exceptions/classcast1.ser")) 116 | (def classcast-exc-parsed (stacktrace/parse-exception classcast-exc)) 117 | 118 | (expect java.lang.ClassCastException (class classcast-exc)) 119 | 120 | (expect java.lang.ClassCastException (:class classcast-exc-parsed)) 121 | 122 | (expect "java.lang.String cannot be cast to java.lang.Number" (:message classcast-exc-parsed)) 123 | 124 | (expect 61 (count (:trace-elems classcast-exc-parsed))) 125 | 126 | (expect {:method "add", :class "clojure.lang.Numbers", :java true, :file "Numbers.java", :line 126} 127 | (first (:trace-elems classcast-exc-parsed))) 128 | 129 | (def the-trace 130 | (list {:method "add", :class "clojure.lang.Numbers", :java true, :file "Numbers.java", :line 126} 131 | {:method "add", :class "clojure.lang.Numbers", :java true, :file "Numbers.java", :line 3523} 132 | {:anon-fn false, :fn "eval9481", :ns "experimental.core-test", :clojure true, 133 | :file "core_test.clj", :line 57} 134 | {:method "eval", :class "clojure.lang.Compiler" 135 | , :java true, :file "Compiler.java", :line 6619} 136 | {:method "eval", :class "clojure.lang.Compiler", :java true, :file "Compiler.java", :line 6582} 137 | {:anon-fn false, :fn "eval", :ns "clojure.core", :clojure true, :file "core.clj", :line 2852} 138 | {:anon-fn false, :fn "run-and-catch", :ns "experimental.core-test", :clojure true, :file "core_test.clj", :line 38} 139 | {:method "applyToHelper", :class "clojure.lang.AFn", :java true, :file "AFn.java", :line 161} 140 | {:method "applyTo", :class "clojure.lang.AFn", :java true, :file "AFn.java", :line 151} 141 | {:method "eval", :class 142 | "clojure.lang.Compiler$InvokeExpr", :java true, :file "Compiler.java", :line 3458} 143 | {:method "eval", :class "clojure.lang.Compiler$DefExpr", :java true, :file "Compiler.java", :line 408} 144 | {:method "eval", :class "clojure.lang.Compiler", :java true, :file "Compiler.java", :line 6624} 145 | {:method "load", :class "clojure.lang.Compiler", :java true, :file "Compiler.java", :line 7064} 146 | {:method "loadResourceScript", :class "clojure.lang.RT", :java true, :file "RT.java", :line 370} 147 | {:method "loadResourceScript", :class "clojure.lang.RT", :java true, :file "RT.java", :line 361} 148 | {:method "load", :class "clojure.lang.RT", :java true, :file "RT.java", :line 440} 149 | {:method "load", :class "clojure.lang.RT", :java true, :file "RT.java", :line 411} 150 | {:anon-fn true, :fn "load", :ns "clojure.core", :clojure true, :file "core.clj", :line 5530} 151 | {:anon-fn false, :fn "load", :ns "clojure.core", 152 | :clojure true, :file "core.clj", :line 5529} 153 | {:method "invoke", :class "clojure.lang.RestFn", :java true, :file "RestFn.java", :line 408} 154 | {:anon-fn false, :fn "load-one", :ns "clojure.core", :clojure true, :file "core.clj", :line 5336} 155 | {:anon-fn true, :fn "load-lib", :ns "clojure.core", :clojure true, :file "core.clj" 156 | , :line 5375} 157 | {:anon-fn false, :fn "load-lib", :ns "clojure.core", :clojure true, :file "core.clj", :line 5374} 158 | {:method "applyTo", :class "clojure.lang.RestFn" 159 | , :java true, :file "RestFn.java", :line 142} 160 | {:anon-fn false, :fn "apply", :ns "clojure.core", :clojure true, :file "core.clj", :line 619} 161 | {:anon-fn false, :fn "load-libs", :ns "clojure.core", :clojure true, :file "core.clj", :line 5413} 162 | {:method "applyTo", :class "clojure.lang.RestFn", :java true, :file "RestFn.java" 163 | , :line 137} 164 | {:anon-fn false, :fn "apply", :ns "clojure.core", :clojure true, :file "core.clj", :line 619} 165 | {:anon-fn false, :fn "require", :ns "clojure.core", :clojure true, :file "core.clj", :line 5496} 166 | {:method "invoke", :class "clojure.lang.RestFn", :java true, :file "RestFn.java", :line 421} 167 | {:anon-fn false, :fn "track-reload-one", :ns "clojure.tools.namespace.reload", :clojure true, :file "reload.clj", :line 35} 168 | {:anon-fn false, :fn "track-reload", :ns "clojure.tools.namespace.reload", :clojure true, :file "reload.clj", :line 52} 169 | {:method "applyToHelper", :class "clojure.lang.AFn", :java true, :file "AFn.java", :line 161} 170 | {:method "applyTo", :class "clojure.lang.AFn", :java true, :file "AFn.java", :line 151} 171 | {:method "alterRoot", :class "clojure.lang.Var", :java true, :file "Var.java" 172 | , :line 336} 173 | {:anon-fn false, :fn "alter-var-root", :ns "clojure.core", :clojure 174 | true, :file "core.clj", :line 4946} 175 | {:method "invoke", :class "clojure.lang.RestFn", :java true, :file "RestFn.java", :line 425} 176 | {:anon-fn false, :fn "do-refresh", :ns "clojure.tools.namespace.repl", :clojure true, :file "repl.clj", :line 94} 177 | {:anon-fn false, :fn "refresh", :ns "clojure.tools.namespace.repl", :clojure 178 | true, :file "repl.clj", :line 142} 179 | {:method "invoke", :class "clojure.lang.RestFn", :java true, :file "RestFn.java", :line 397} 180 | {:anon-fn false, :fn "refresh-environment", :ns "autoexpect.runner", :clojure true, :file "runner.clj", :line 23} 181 | {:anon-fn true, :fn "run-tests", :ns "autoexpect.runner", :clojure true, :file "runner.clj", :line 50} 182 | {:anon-fn false, :fn "run-tests", :ns "autoexpect.runner", :clojure true, :file "runner.clj", :line 50} 183 | {:anon-fn true, :fn "monitor-project", :ns "autoexpect.runner", :clojure true, :file "runner.clj", :line 69} 184 | {:anon-fn true, :fn "monitor-project", :ns "autoexpect.runner", :clojure true, :file "runner.clj", :line 68} 185 | {:anon-fn false, :fn "monitor-project", :ns "autoexpect.runner", :clojure true, :file "runner.clj", :line 66} 186 | {:anon-fn false, :fn "eval1187", :ns "user", :clojure true, :file "form-init6834699387848419871.clj", 187 | :line 1} 188 | {:method "eval", :class "clojure.lang.Compiler", :java true, :file "Compiler.java", :line 6619} 189 | {:method "eval", :class "clojure.lang.Compiler", :java true, :file "Compiler.java", :line 6609} 190 | {:method "load", :class "clojure.lang.Compiler", :java true, :file "Compiler.java", :line 7064} 191 | {:method "loadFile", :class "clojure.lang.Compiler", :java true, :file "Compiler.java", :line 7020} 192 | {:anon-fn false, :fn "load-script", :ns "clojure.main", :clojure true, :file "main.clj", :line 294} 193 | {:anon-fn false, :fn "init-opt", :ns "clojure.main", :clojure true, :file "main.clj", :line 299} 194 | {:anon-fn false, :fn "initialize", :ns "clojure.main", :clojure true, :file "main.clj", :line 327} 195 | {:anon-fn false, :fn "null-opt", :ns "clojure.main", :clojure true, :file "main.clj", :line 362} 196 | {:anon-fn false, :fn "main", :ns "clojure.main", :clojure true, :file "main.clj", :line 440} 197 | {:method "invoke", :class "clojure.lang.RestFn", :java true, :file "RestFn.java", :line 421} 198 | {:method "invoke", :class "clojure.lang.Var", :java true, :file "Var.java", :line 419} 199 | {:method "applyToHelper", :class "clojure.lang.AFn", :java true, :file "AFn.java", :line 163} 200 | {:method "applyTo", :class "clojure.lang.Var", :java true, :file "Var.java", :line 532} 201 | {:method "main", :class "clojure.main", :java true, :file "main.java", :line 37})) 202 | 203 | (expect the-trace (:trace-elems classcast-exc-parsed)) 204 | 205 | ;*** Testing trace->string **** 206 | 207 | ;; copied the matching string for easier testing: 208 | ;(#(str "\t" (:ns %) "/" (:fn %) " (" (:file %) " line " (:line %) ")") 209 | (expect "\tautoexpect.runner/run-tests (runner.clj line 50)" 210 | (trace-elem->string {:anon-fn true, :fn "run-tests", :ns "autoexpect.runner", :clojure true, :file "runner.clj", :line 50} "\t")) 211 | 212 | ;############################################# 213 | ;### 6. Testing in dictionaries.clj ### 214 | ;############################################# 215 | 216 | ;; type-dictionary tests 217 | (expect "a number" (type-dictionary :java.lang.Float)) 218 | (expect "a number" (:java.lang.Float type-dictionary)) 219 | (expect "a regular expression matcher" (type-dictionary :java.util.regex.Matcher)) 220 | 221 | ;; really simple testing for general-types 222 | (expect "a vector" (second (general-types 1))) 223 | 224 | ;; testing for best-approximation 225 | (expect "unrecognized type atom" (best-approximation 'atom)) 226 | (expect "a map" (best-approximation 'clojure.lang.IPersistentMap)) 227 | (expect "a collection" (best-approximation 'IPersistentStack)) \ 228 | ; this checks that the function best-approximation can add in 229 | ; "clojure.lang." by itself 230 | 231 | ;; testing for get-type 232 | (expect "a boolean" (get-type 'java.lang.Boolean)) 233 | (expect "unrecognized type bogus_thingy" (get-type 'bogus_thingy)) 234 | 235 | ;; testing for predefined-names 236 | (expect "+" (predefined-names :_PLUS_)) 237 | (expect "/" (:_SLASH_ predefined-names)) 238 | (expect "-" (predefined-names :_)) 239 | (expect nil (:_UNKNOWN_ predefined-names)) 240 | 241 | ;; testing for lookup-funct-name 242 | (expect "/" (lookup-funct-name '_SLASH_)) 243 | (expect "*" (lookup-funct-name "_STAR_")) 244 | (expect "testingRocks" (lookup-funct-name 'testingRocks)) 245 | 246 | ;; testing for get-function-name 247 | (expect "*" (get-function-name "clojure.core$_STAR_")) 248 | (expect "even?" (get-function-name "clojure.core$even_QMARK_")) 249 | (expect "anonymous-function" (get-function-name "intro.core$eval6393$fn__6394")) 250 | (expect "-" (get-function-name "clojure.core$_")) 251 | (expect ">=" (get-function-name "corefns.corefns$_GT__EQ_")) 252 | (expect "swap!" (get-function-name "clojure.core$swap_BANG_")) 253 | (expect "anonymous-function" (get-function-name "intro.core$eval6397$fn__6398")) 254 | (expect "/" (get-function-name "clojure.core$_SLASH_")) 255 | 256 | ;; testing for get-macro-name 257 | (expect "regex" (get-macro-name "clojure.string/regex")) 258 | (expect "->>" (get-macro-name "clojure.core/->>")) 259 | (expect "lazy-cat" (get-macro-name "clojure.core/lazy-cat")) 260 | (expect "cond" (get-macro-name "clojure.core/cond")) 261 | (expect "and" (get-macro-name "clojure.core/and")) 262 | 263 | ;; testing for arg-str 264 | (expect "first argument" (arg-str 1)) 265 | (expect "21st argument" (arg-str 21)) 266 | (expect "210528th argument" (arg-str 210528)) 267 | 268 | ;; testing for lookup-arity 269 | (expect "at least two" (lookup-arity "map")) 270 | (expect "one" (lookup-arity "count")) 271 | (expect nil (lookup-arity "concat")) 272 | (expect "two or three" (lookup-arity "reduce")) 273 | (expect nil (lookup-arity "anonymous function")) 274 | (expect nil (lookup-arity "not a function")) 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | -------------------------------------------------------------------------------- /test/errors/testing_tools.clj: -------------------------------------------------------------------------------- 1 | (ns errors.testing_tools 2 | (:require [expectations :refer :all] 3 | [clj-stacktrace.core :as stacktrace] 4 | [errors.prettify_exception :refer :all] 5 | [errors.messageobj :refer :all] 6 | [utilities.stacktrace_functions :refer :all] 7 | [corefns.collection_fns :refer :all] 8 | [utilities.file_IO :refer :all]) 9 | (:import [java.io.FileInputStream] 10 | [java.io.ObjectInputStream] 11 | [java.io.FileOutputStream] 12 | [java.io.ObjectOutputStream] 13 | [java.util.ArrayList])) 14 | 15 | 16 | ; Add files to this set when you make them. 17 | (def saved-exceptions 18 | #{"4clojure-prob134-AssertionError.ser" 19 | "4clojure-prob15-ClassCast.ser" 20 | "4clojure-prob156-AssertionError.ser" 21 | "4clojure-prob16-Arity.ser" 22 | "4clojure-prob17-NullPointer.ser" 23 | "4clojure-prob18-AssertionError.ser" 24 | "4clojure-prob20-AssertionError.ser" 25 | "4clojure-prob21-ArityException.ser" 26 | "4clojure-prob24-pre-plus-rewrite-ClassCast.ser" 27 | "4clojure-prob57-ArityException.ser" 28 | "4clojure-prob64-NullPointer.ser" 29 | "add-five-IllegalArgException.ser" 30 | "DrRacket-Exercise2-ClassCast.ser" 31 | "4clojure-prob23-IndexOutOfBounds.ser" 32 | "4clojure-prob27-ArityException.ser" 33 | "DrRacket-Exercise9-ArityException.ser" 34 | "4clojure-prob38-ArityException.ser" 35 | "DrRacket-Exercise3-IndexOutOfBounds.ser"}) 36 | 37 | ;;; INDEX ;;; 38 | 39 | ;1 Generating exceptions 40 | ;|-1.1 functions 41 | ;|-1.2 tests 42 | ;2. Comparing Stacktraces 43 | ;|-2.1 functions 44 | ;|-2.2 tests 45 | ;3. Testing for hints 46 | ;4. More Real-Life Exceptions 47 | ;5. Comparing top elements of stacktraces 48 | ;|-5.1 functions 49 | ;|-5.2 prints 50 | ;|-5.3 tests 51 | 52 | ;############################## 53 | ;## 1. Generating Exceptions ## 54 | ;############################## 55 | 56 | ; 1.1 functions 57 | 58 | (defn run-and-catch-raw 59 | "A function that takes quoted code and runs it, attempting to catch any 60 | exceptions it may throw. Returns the exeception or nil. If a quoted namespace is 61 | given, it runs the code in that namespace.Attempted to use a symbol, but unrecognized type clojure.lang.Namespace was expected." 62 | ([code] 63 | (try 64 | (eval code) 65 | (catch Throwable e e))) 66 | ([name-space code] 67 | (try 68 | (let [our-ns (find-ns name-space)] 69 | ;(print (filter (fn [x] (re-matches #".*intro.*" (str x))) (all-ns))) 70 | ;(print (str "printing namespace: " (nil? our-ns))) 71 | (binding [*ns* our-ns] (eval code))) 72 | (catch Throwable e e)))) 73 | 74 | (defn run-and-catch-pretty-no-stacktrace 75 | "A function that takes quoted code and runs it, attempting to catch any 76 | exceptions it may throw. Returns the prettified exception without a stacktrace. If a quoted 77 | namespace is given, it runs the code in that namespace." 78 | ([code] 79 | ;(println (str "MSG: " (count (:msg-info-obj (prettify-exception (run-and-catch-raw code)))))) 80 | (:msg-info-obj (prettify-exception (run-and-catch-raw code)))) 81 | ([name-space code] 82 | ;(println (str "MSG: " (count (:msg-info-obj (prettify-exception (run-and-catch-raw name-space code))))) 83 | (:msg-info-obj (prettify-exception (run-and-catch-raw name-space code))))) 84 | 85 | ;; when giving a namespace, pass in a quoted namespace 86 | (defn run-and-catch-pretty-with-stacktrace 87 | "A function that takes quoted code and runs it, attempting to catch any 88 | exceptions it may throw. Returns the prettified exception with a stacktrace. If a quoted namespace is given, 89 | it runs the code in that namespace." 90 | ([code] 91 | (prettify-exception (run-and-catch-raw code))) 92 | ([name-space code] 93 | (prettify-exception (run-and-catch-raw name-space code)))) 94 | 95 | (defn exception->string 96 | "Converts exceptions to strings, returning a string or the original e if it 97 | is not an exception" 98 | [e] (if (instance? Throwable e) (.getMessage e) e)) 99 | 100 | (defn get-text-no-location [m] 101 | ;(println (str "MESSAGE" (get-all-text m))) 102 | (let [text-no-newln (nth (re-matches #"(.*)\nFound(.*)" (get-all-text m)) 1) 103 | text (if text-no-newln 104 | text-no-newln 105 | (let [matches (re-matches #"(.*)\n(.*)\nFound(.*)" (get-all-text m))] 106 | (str (nth matches 1) "\n" (nth matches 2))))] 107 | text)) 108 | 109 | 110 | ; 1.2 tests 111 | 112 | (expect "java.lang.Long cannot be cast to clojure.lang.IFn" 113 | (exception->string (run-and-catch-raw '(1 3)))) 114 | 115 | ;; Hmmm??? - there's no exception here, is there? 116 | (expect 3 117 | (exception->string (run-and-catch-raw '(+ 1 2)))) 118 | 119 | ;############################## 120 | ;## 2. Comparing Stacktraces ## 121 | ;############################## 122 | 123 | ; 2.1 functions 124 | 125 | (defn get-keyword-in-stacktrace 126 | "Gets all of the keywords mentioned in a parsed stacktrace" 127 | [a-keyword trace] 128 | (filter 129 | (fn [ele] 130 | (not (nil? ele))) 131 | (map a-keyword (:trace-elems trace)))) 132 | 133 | (defn- get-keyword-but-not-x-in-stacktrace 134 | "Don't actually use this - wrap it in a helper function. Gets all the values of a keyword mentioned in a 135 | parsed stacktrace, except those that the predicate returns true for" 136 | [a-keyword pred trace] 137 | (filter 138 | (fn [ele] 139 | (not (or (nil? ele) 140 | (pred ele)))) 141 | (map a-keyword (:trace-elems trace)))) 142 | 143 | ;; an example of what get-keyword-but-not-x-in-stacktrace 144 | (defn get-fns-in-stacktrace 145 | "Gets all of the functions mentioned in a parsed stacktrace" 146 | [trace] 147 | (get-keyword-but-not-x-in-stacktrace 148 | :fn 149 | (fn [ele] (not (clojure.string/blank? (re-matches #"eval\d.*" ele)))) 150 | trace)) 151 | 152 | 153 | (defn get-eval-nums 154 | "Gets all evaulation numbers - a random number (confirm?) that is attached to all evals in the stacktrace. Used 155 | to confirm that two stacktraces came from the same exception." 156 | [trace] 157 | (map (fn [ele] ele) 158 | (get-keyword-in-stacktrace :fn trace))) 159 | 160 | ; 2.2 tests 161 | 162 | (def ex1 (run-and-catch-raw '(+ 2 "pie"))) 163 | 164 | (expect "eval" (first (get-fns-in-stacktrace (stacktrace/parse-exception ex1)))) 165 | 166 | ;################################## 167 | ;## 3. Testing for hints ## 168 | ;################################## 169 | 170 | (expect :class-cast-exception (:key (first-match ClassCastException 171 | "java.lang.String cannot be cast to clojure.core.Number"))) 172 | 173 | ;; because this is now an assertion error, no hint is actually written yet 174 | ;(expect #"The error happens when a function's argument is not of the type for which the function is defined." 175 | ; (:hints (prettify-exception (run-and-catch-raw '(+ 2 "string"))))) 176 | 177 | ;; Elena commented out testing for hints since we currently don't have hints 178 | 179 | ;(expect #"Make sure you have the correct number of arguments" 180 | ; (:hints (prettify-exception (run-and-catch-raw '(assoc {1 2} 3))))) 181 | 182 | (expect :assertion-error-with-argument (:key (first-match AssertionError "Assert failed: (check-if-sequable? \"filter\" argument2)"))) 183 | 184 | ;(expect "" (:hints (prettify-exception (run-and-catch-raw 'intro.core '(filter odd? 5))))) 185 | 186 | -------------------------------------------------------------------------------- /test/exception_msgs/overwritten_fns/higher_order_fns.clj: -------------------------------------------------------------------------------- 1 | (ns exception_msgs.overwritten_fns.higher_order_fns 2 | (:use [errors.prettify_exception :only [line-number-format prettify-exception]]) 3 | (:require [expectations :refer :all] 4 | [errors.messageobj :refer :all] 5 | [errors.testing_tools :refer :all] 6 | [utilities.file_IO :refer :all] 7 | )) 8 | 9 | ;; testing for :class-cast-exception-cannot-cast-to-map-entry 10 | (expect "Attempted to create a map using a keyword, but a sequence of vectors of length 2 or a sequence of maps is needed." 11 | (get-text-no-location 12 | (run-and-catch-pretty-no-stacktrace 'intro.core 13 | '(into {} [#{:x :m} #{:q :b}])))) 14 | 15 | 16 | ;; Note: the order of elements in a hashmap is not guaranteed, so the actual elements amy be different 17 | (expect "In function +, the argument {:a 6, :k \"a\", :b 4, :c +, :l 6, :m 7} must be a number but is a map,\nin the function call (+ {:a 6, :k \"a\", :b 4, :c +, :l 6, :m 7})" 18 | (get-text-no-location 19 | (run-and-catch-pretty-no-stacktrace 'intro.student '(+ {:a 6 :k "a" :b 4 :c + :l 6 :m 7})))) 20 | 21 | ;; testing for :illegal-argument-no-val-supplied-for-key 22 | (expect "No value found for key d. Every key for a hash-map must be followed by a value." 23 | (get-text-no-location 24 | (run-and-catch-pretty-no-stacktrace 'intro.student '(hash-map "c" :d "d")))) 25 | 26 | ;; testing for :illegal-argument-vector-arg-to-map-conj 27 | (expect "Vectors added to a map must consist of two elements: a key and a value." 28 | (get-text-no-location 29 | (run-and-catch-pretty-no-stacktrace 'intro.student '(into {} [[1 2] [3]])))) 30 | 31 | ;; testing for invalid token error 32 | (expect #"You cannot use : in this position." 33 | (get-text-no-location (:msg-info-obj (try (load-file "exceptions/compilation_errors/invalid_token_error2.clj") 34 | (catch Throwable e (prettify-exception e)))))) 35 | 36 | (expect "Vectors added to a map must consist of two elements: a key and a value." 37 | (get-text-no-location 38 | (run-and-catch-pretty-no-stacktrace 'intro.student '(merge {:a 1 :b 2} [1 2 3] [1 2] [1 5])))) 39 | 40 | (expect "Vectors added to a map must consist of two elements: a key and a value." 41 | (get-text-no-location 42 | (run-and-catch-pretty-no-stacktrace 'intro.student '(conj {:a 1 :b 2} [1 2 3 4])))) 43 | 44 | ;; testing for :illegal-argument-even-number-of-forms 45 | (expect #"Parameters for let must come in pairs, but one of them does not have a match; (.*)" 46 | (get-text-no-location (:msg-info-obj (try (load-file "exceptions/compilation_errors/let-no-matching-pair.clj") 47 | (catch Throwable e (prettify-exception e)))))) 48 | 49 | (expect #"When declaring a let, you need to pass it a vector of arguments.(.*)" 50 | (get-text-no-location (:msg-info-obj (try (load-file "exceptions/compilation_errors/let-odd-number-bindings.clj") 51 | (catch Throwable e (prettify-exception e)))))) 52 | 53 | ;; testing for # must be followed by a symbol error 54 | (expect #"# must be followed by a symbol." 55 | (get-text-no-location (:msg-info-obj (try (load-file "exceptions/compilation_errors/#_must_be_followed_by_symbol.clj") 56 | (catch Throwable e (prettify-exception e)))))) 57 | 58 | ;; testing for wrong number of args to a keyword 59 | (expect #"A keyword: :a can only take one or two arguments." 60 | (get-text-no-location (:msg-info-obj (try (load-file "exceptions/compilation_errors/keyword_wrong_number_of_args.clj") 61 | (catch Throwable e (prettify-exception e)))))) 62 | 63 | (expect #"A keyword: :a can only take one or two arguments." 64 | (get-text-no-location (:msg-info-obj (try (load-file "exceptions/compilation_errors/keyword_wrong_number_of_args2.clj") 65 | (catch Throwable e (prettify-exception e)))))) 66 | 67 | ;; testing for :illegal-argument-parameters-must-be-in-vector 68 | (expect #"Parameters for defn must be a vector, but my-argument was found instead\.(.*)" 69 | (get-all-text 70 | (run-and-catch-pretty-no-stacktrace 'intro.student '(defn my-function my-argument)))) 71 | 72 | (expect #"Parameters for defn must be a vector, but 5 was found instead\.(.*)" 73 | (get-all-text 74 | (run-and-catch-pretty-no-stacktrace 'intro.student '(defn my-function 5)))) 75 | 76 | (expect #"Parameters for defn must be a vector, but \+ was found instead\.(.*)" 77 | (get-all-text 78 | (run-and-catch-pretty-no-stacktrace 'intro.student '(defn my-function (+ x y))))) 79 | 80 | (expect #"Parameters for defn must be a vector, but \+ was found instead\.(.*)" 81 | (get-all-text 82 | (run-and-catch-pretty-no-stacktrace 'intro.student '(defn my-function + x y)))) 83 | 84 | ;; testing for :illegal-argument-exactly-2-forms 85 | (expect #"The function when-let requires exactly 2 forms in binding vector. Line (.*) in the file intro.core" 86 | (get-all-text (run-and-catch-pretty-no-stacktrace 'intro.core '(when-let [num1 1 num2 2] "hello")))) 87 | 88 | (expect "In function dissoc, the argument :s must be a hashmap but is a keyword,\nin the function call (dissoc :s)" 89 | (get-text-no-location 90 | (run-and-catch-pretty-no-stacktrace 'intro.student '(dissoc :s)))) 91 | 92 | (expect "% must be either on its own or followed by a number or &." 93 | (get-text-no-location (:msg-info-obj (try (load-file "exceptions/compilation_errors/percent_followed_by_letter.clj") 94 | (catch Throwable e (prettify-exception e)))))) 95 | 96 | ;; testing for :index-out-of-bounds-index-not-provided 97 | (expect "An index in a sequence is out of bounds or invalid." 98 | (get-text-no-location 99 | (run-and-catch-pretty-no-stacktrace 'intro.core '(nth [0 1 2 3 4 5] 10)))) 100 | 101 | ;; testing for :arity-exception-wrong-number-of-arguments 102 | (expect "You cannot pass three arguments to a function even?, need one,\nin the function call (even? 3 6 1)" 103 | (get-text-no-location (run-and-catch-pretty-no-stacktrace 'intro.core '(even? 3 6 1)))) 104 | 105 | (expect "You cannot pass zero arguments to a function even?, need one,\nin the function call (even? )" 106 | (get-text-no-location (run-and-catch-pretty-no-stacktrace 'intro.core '(even?)))) 107 | 108 | (expect "You cannot pass one argument to this anonymous function." 109 | (get-text-no-location (run-and-catch-pretty-no-stacktrace 'intro.core '(#(+ %1 %2) 1)))) 110 | 111 | (expect "You cannot pass four arguments to a function user-def-fcn." 112 | (get-text-no-location (run-and-catch-pretty-no-stacktrace 'intro.core '((defn user-def-fcn [x] (+ x 1)) 113 | (user-def-fcn 1 2 3 4))))) 114 | 115 | (expect "You cannot use the same key in a hash map twice, but you have duplicated the key :b." 116 | ;; note: let is needed in the test since the error must happen only at run time 117 | (get-text-no-location (run-and-catch-pretty-no-stacktrace 'intro.core '(let [a :b b :b] {a 1 b 2})))) 118 | 119 | ;; testing for :unsupported-operation-wrong-type-of-argument 120 | (expect "Function nth does not allow a map as an argument." 121 | (get-text-no-location (run-and-catch-pretty-no-stacktrace 'intro.core '(nth {:a 10 :z 4} 20)))) 122 | 123 | ;;;; Elena: we might want to revisit this one 124 | (expect #"Parameters for loop must come in pairs, but one of them does not have a match; on line (\d+) in the file intro\.core" 125 | (get-text-no-location (run-and-catch-pretty-no-stacktrace 'intro.core '(defn s [s] (loop [s]))))) 126 | 127 | (expect "This recur is supposed to take zero arguments, but you are passing one." ; this is giving NO_SOURCE_PATH 128 | (get-text-no-location (run-and-catch-pretty-no-stacktrace 'intro.core '(recur (inc 1))))) 129 | 130 | (expect "This recur is supposed to take one argument, but you are passing two." ; 131 | (get-text-no-location (run-and-catch-pretty-no-stacktrace 'intro.core '(loop [x 1] (recur (inc x) (dec x)))))) 132 | 133 | (expect "Parameters for cond must come in pairs, but one of them does not have a match." ; this is giving NO_SOURCE_PATH 134 | (get-text-no-location 135 | (run-and-catch-pretty-no-stacktrace 'intro.core '(defn my-num [x] (cond (= 1 x)))))) 136 | 137 | (expect #"loop is a macro, cannot be passed to a function." ; this is giving NO_SOURCE_PATH 138 | (get-text-no-location (run-and-catch-pretty-no-stacktrace 'intro.core '(defn my-happy [x] loop [x x])))) 139 | 140 | (expect #"Name banana is undefined." ; this is giving NO_SOURCE_PATH 141 | (get-text-no-location (run-and-catch-pretty-no-stacktrace 'intro.core '(banana 5 6)))) 142 | 143 | (expect (str "There is an unmatched delimiter ).\nFound in file unmatched_delimiter.clj" (line-number-format 3 20) ".") 144 | (get-all-text (:msg-info-obj (prettify-exception (import-from-file "exceptions/unmatched_delimiter.ser"))))) 145 | 146 | (expect "Too many arguments to def." ; this is giving NO_SOURCE_PATH 147 | (get-text-no-location (run-and-catch-pretty-no-stacktrace 'intro.core '(def my-var 5 6)))) 148 | 149 | (expect "Too few arguments to def." ; this is giving NO_SOURCE_PATH 150 | (get-text-no-location (run-and-catch-pretty-no-stacktrace 'intro.core '(def)))) 151 | -------------------------------------------------------------------------------- /test/exception_msgs/overwritten_fns/num_fns.clj: -------------------------------------------------------------------------------- 1 | (ns exception_msgs.overwritten_fns.num_fns 2 | (:use [errors.prettify_exception :only [prettify-exception]]) 3 | (:require [expectations :refer :all] 4 | [errors.messageobj :refer :all] 5 | [errors.testing_tools :refer :all] 6 | [utilities.file_IO :refer :all] 7 | )) 8 | 9 | 10 | ;; Since spec evaluates the arguments, the tests below are no longer passing 11 | 12 | ;; testing for :pretty-print-value 13 | ;(expect "In function +, the second argument ((0 1 2 3...) (0 1 2 3...) (0 1 2 3...) (0 1 2 3...) (0 1 2 3...) (0 1 2 3...) (0 1 2 3...) (0 1 2 3...) (0 1 2 3...) (0 1 2 3...)...) must be a number but is a sequence." 14 | ; (get-text-no-location 15 | ; (run-and-catch-pretty-no-stacktrace 'intro.student '(+ 1 (repeat (range)))))) 16 | 17 | ;(expect "In function +, the second argument (((...) (...) (...) (...)...) ((...) (...) (...) (...)...) ((...) (...) (...) (...)...) ((...) (...) (...) (...)...) ((...) (...) (...) (...)...) ((...) (...) (...) (...)...) ((...) (...) (...) (...)...) ((...) (...) (...) (...)...) ((...) (...) (...) (...)...) ((...) (...) (...) (...)...)...) must be a number but is a sequence." 18 | ; (get-text-no-location 19 | ; (run-and-catch-pretty-no-stacktrace 'intro.student '(+ 1 (repeat (repeat (range))))))) 20 | ; 21 | ;(expect "In function inc, the argument ((0 1 2 3...) [1 (...) (...) 4...] (1 2 1 2...) (0 1 2 3...) [1 (...) (...) 4...] (1 2 1 2...) (0 1 2 3...) [1 (...) (...) 4...] (1 2 1 2...) (0 1 2 3...)...) must be a number but is a sequence." 22 | ; (get-text-no-location 23 | ; (run-and-catch-pretty-no-stacktrace 'intro.student '(inc (cycle [(range) [1 (repeat 1) (range) 4 5 6 7 8 9 245 4235 5423] (cycle [1 2])]))))) 24 | 25 | ;(expect "In function +, the second argument [1 2 3 4 5 6 76 (0 1 2 3...) 756 354...] must be a number but is a vector." 26 | ; (get-text-no-location 27 | ; (run-and-catch-pretty-no-stacktrace 'intro.student '(+ 1 [1 2 3 4 5 6 76 (range) 756 354 6 645])))) 28 | 29 | ;; testing for pretty-print-value for functions within sequences and for vectors 30 | 31 | (expect "In function +, the argument [1 2] must be a number but is a vector,\nin the function call (+ [1 2])" 32 | (get-text-no-location 33 | (run-and-catch-pretty-no-stacktrace 'intro.student '(+ [1 2])))) 34 | 35 | (expect "In function +, the argument [+] must be a number but is a vector,\nin the function call (+ [+])" 36 | (get-text-no-location 37 | (run-and-catch-pretty-no-stacktrace 'intro.student '(+ [+])))) 38 | 39 | (expect "In function +, the argument [map] must be a number but is a vector,\nin the function call (+ [map])" 40 | (get-text-no-location 41 | (run-and-catch-pretty-no-stacktrace 'intro.student '(+ [map])))) 42 | 43 | (expect "In function +, the argument (\"a\" \"b\") must be a number but is a list,\nin the function call (+ (\"a\" \"b\"))" 44 | (get-text-no-location 45 | (run-and-catch-pretty-no-stacktrace 'intro.student '(+ '("a" "b"))))) 46 | 47 | (expect "In function +, the argument #{anonymous-function} must be a number but is a set,\nin the function call (+ #{anonymous-function})" 48 | (get-text-no-location 49 | (run-and-catch-pretty-no-stacktrace 'intro.student '(+ #{#(+ % 2)})))) 50 | 51 | (expect "In function +, the argument #{+} must be a number but is a set,\nin the function call (+ #{+})" 52 | (get-text-no-location 53 | (run-and-catch-pretty-no-stacktrace 'intro.student '(+ #{+})))) 54 | 55 | (expect "In function +, the argument {:a +} must be a number but is a map,\nin the function call (+ {:a +})" 56 | (get-text-no-location 57 | (run-and-catch-pretty-no-stacktrace 'intro.student '(+ {:a +})))) 58 | 59 | (expect "In function +, the argument {:a {6 \"a\", 7 true}, :b 4, :c +} must be a number but is a map,\nin the function call (+ {:a {6 \"a\", 7 true}, :b 4, :c +})" 60 | (get-text-no-location 61 | (run-and-catch-pretty-no-stacktrace 'intro.student '(+ {:a {6 "a" 7 true} :b 4 :c +})))) 62 | 63 | ;; Note: the order of elements in a hashmap is not guaranteed, so the actual elements amy be different 64 | (expect "In function +, the argument {:a 6, :k \"a\", :b 4, :c +, :l 6, :m 7} must be a number but is a map,\nin the function call (+ {:a 6, :k \"a\", :b 4, :c +, :l 6, :m 7})" 65 | (get-text-no-location 66 | (run-and-catch-pretty-no-stacktrace 'intro.student '(+ {:a 6 :k "a" :b 4 :c + :l 6 :m 7})))) 67 | 68 | ;; testing for invalid number exception 69 | (expect #"Invalid number: 1.2.2." 70 | (get-text-no-location (:msg-info-obj (try (load-file "exceptions/compilation_errors/invalid_number.clj") 71 | (catch Throwable e (prettify-exception e)))))) 72 | 73 | ;; testing for :illegal-argument-type-not-supported 74 | (expect "Function contains? does not allow a sequence as an argument." 75 | (get-text-no-location 76 | (run-and-catch-pretty-no-stacktrace 'intro.student '(contains? (seq [1 3 6]) 2)))) 77 | 78 | ;; testing for odd? and even? 79 | (expect "In function even?, the argument must be an integer number but is nil,\nin the function call (even? nil)" 80 | (get-text-no-location 81 | (run-and-catch-pretty-no-stacktrace 'intro.student '(even? nil)))) 82 | 83 | (expect "In function odd?, the argument must be an integer number but is nil,\nin the function call (odd? nil)" 84 | (get-text-no-location 85 | (run-and-catch-pretty-no-stacktrace 'intro.student '(odd? nil)))) 86 | 87 | ;; (expect "In function even?, the argument [(0 1 2 3...) (0 1 2 3...)] must be an integer number but is a vector." 88 | ;; (get-text-no-location 89 | ;; (run-and-catch-pretty-no-stacktrace 'intro.student '(even? [(range) (range)])))) 90 | 91 | (expect "In function dec, the argument :r must be a number but is a keyword." 92 | (get-text-no-location 93 | (run-and-catch-pretty-no-stacktrace 'intro.student '(dec :r)))) 94 | 95 | ;moved from core_fns_behavior 96 | 97 | (expect Exception (-)) 98 | (expect Exception (/)) 99 | (expect Exception (max)) 100 | (expect Exception (min)) 101 | -------------------------------------------------------------------------------- /test/exception_msgs/overwritten_fns/seq_ops.clj: -------------------------------------------------------------------------------- 1 | (ns exception_msgs.overwritten_fns.seq_ops 2 | (:use [errors.prettify_exception :only [line-number-format prettify-exception]]) 3 | (:require [expectations :refer :all] 4 | [errors.messageobj :refer :all] 5 | [errors.testing_tools :refer :all] 6 | [utilities.file_IO :refer :all] 7 | )) 8 | 9 | ;; testing for :illegal-argument-no-val-supplied-for-key 10 | (expect "No value found for key d. Every key for a hash-map must be followed by a value." 11 | (get-text-no-location 12 | (run-and-catch-pretty-no-stacktrace 'intro.student '(hash-map "c" :d "d")))) 13 | 14 | ;; testing for :illegal-argument-vector-arg-to-map-conj 15 | (expect "Vectors added to a map must consist of two elements: a key and a value." 16 | (get-text-no-location 17 | (run-and-catch-pretty-no-stacktrace 'intro.student '(into {} [[1 2] [3]])))) 18 | 19 | (expect "Vectors added to a map must consist of two elements: a key and a value." 20 | (get-text-no-location 21 | (run-and-catch-pretty-no-stacktrace 'intro.student '(merge {:a 1 :b 2} [1 2 3] [1 2] [1 5])))) 22 | 23 | (expect "Vectors added to a map must consist of two elements: a key and a value." 24 | (get-text-no-location 25 | (run-and-catch-pretty-no-stacktrace 'intro.student '(conj {:a 1 :b 2} [1 2 3 4])))) 26 | 27 | ;; testing for :illegal-argument-cannot-convert-type 28 | (expect "In function cons, the second argument 2 must be a sequence but is a number,\nin the function call (cons 1 2)" 29 | (get-text-no-location 30 | (run-and-catch-pretty-no-stacktrace 'intro.student '(cons 1 2)))) 31 | 32 | (expect #"You cannot use / in this position." 33 | (get-text-no-location (:msg-info-obj (try (load-file "exceptions/compilation_errors/invalid_token_error1.clj") 34 | (catch Throwable e (prettify-exception e)))))) 35 | 36 | ;; testing for wrong number of args to a keyword 37 | (expect #"A keyword: :a can only take one or two arguments." 38 | (get-text-no-location (:msg-info-obj (try (load-file "exceptions/compilation_errors/keyword_wrong_number_of_args.clj") 39 | (catch Throwable e (prettify-exception e)))))) 40 | 41 | ;; testing for greater than 20 arguments 42 | (expect #"A function may not take more than 20 parameters." 43 | (get-text-no-location (:msg-info-obj (try (load-file "exceptions/compilation_errors/greater_than_20_parameters.clj") 44 | (catch Throwable e (prettify-exception e)))))) 45 | 46 | (expect #"A function may not take more than 20 parameters." 47 | (get-text-no-location (:msg-info-obj (try (load-file "exceptions/compilation_errors/greater_than_20_parameters2.clj") 48 | (catch Throwable e (prettify-exception e)))))) 49 | 50 | (expect "Cannot call nil as a function." 51 | (get-text-no-location 52 | (run-and-catch-pretty-no-stacktrace 'intro.student '(nil 5)))) 53 | 54 | (expect "In function empty?, the argument :s must be a sequence but is a keyword,\nin the function call (empty? :s)" 55 | (get-text-no-location 56 | (run-and-catch-pretty-no-stacktrace 'intro.student '(empty? :s)))) 57 | 58 | (expect "In function first, the argument :e must be a sequence but is a keyword." 59 | (get-text-no-location 60 | (run-and-catch-pretty-no-stacktrace 'intro.student '(first :e)))) 61 | 62 | (expect "In function rest, the argument :a must be a sequence but is a keyword." 63 | (get-text-no-location 64 | (run-and-catch-pretty-no-stacktrace 'intro.student '(rest :a)))) 65 | 66 | (expect "In function next, the argument :n must be a sequence but is a keyword." 67 | (get-text-no-location 68 | (run-and-catch-pretty-no-stacktrace 'intro.student '(next :n)))) 69 | 70 | (expect "In function seq, the argument :w must be a sequence but is a keyword." 71 | (get-text-no-location 72 | (run-and-catch-pretty-no-stacktrace 'intro.student '(seq :w)))) 73 | 74 | (expect "In function count, the argument :a must be a sequence but is a keyword." 75 | (get-text-no-location 76 | (run-and-catch-pretty-no-stacktrace 'intro.student '(count :a)))) 77 | 78 | ;; testing for :index-out-of-bounds-index-provided 79 | (expect "An index in a sequence is out of bounds. The index is: 10." 80 | (get-text-no-location 81 | (run-and-catch-pretty-no-stacktrace 'intro.core '(throw (new IndexOutOfBoundsException "10"))))) 82 | 83 | ;; testing for :null-pointer-non-existing-object-provided 84 | (expect "An attempt to access a non-existing object: some message (NullPointerException)." 85 | (get-text-no-location (run-and-catch-pretty-no-stacktrace 'intro.core '(throw (new NullPointerException "some message"))))) 86 | 87 | ;; testing for :null-pointer-non-existing-object-not-provided 88 | (expect "An attempt to access a non-existing object (NullPointerException)." 89 | (get-text-no-location (run-and-catch-pretty-no-stacktrace 'intro.core '(int nil)))) 90 | 91 | (expect #"The arguments following the map or vector in assoc must come in pairs, but one of them does not have a match." 92 | (get-text-no-location (run-and-catch-pretty-no-stacktrace 'intro.core '(assoc {:a 3 :b 5} :key1 "val1" :key2)))) 93 | 94 | ;; the internal representation of zero? is zero?--inliner--4238 (in this particular test), i.e. it has 95 | ;; an inliner part 96 | 97 | (expect "You cannot pass zero arguments to a function zero?." 98 | (get-text-no-location (run-and-catch-pretty-no-stacktrace 'intro.core '(zero?)))) 99 | 100 | (expect "Recur can only occur as a tail call: no operations can be done after its return." ; this is giving NO_SOURCE_PATH 101 | (get-text-no-location (run-and-catch-pretty-no-stacktrace 'intro.core '(defn inc-nums [x] ((recur (inc x)) (loop [x x])))))) 102 | 103 | (expect #"def must be followed by a name." ; this is giving NO_SOURCE_PATH 104 | (get-text-no-location (run-and-catch-pretty-no-stacktrace 'intro.core '(def 4 (+ 2 2))))) 105 | 106 | (expect (str "End of file, starting at line 3.\nProbably a non-closing parenthesis or bracket.\nFound in file eof.clj" (line-number-format 4 1) ".") 107 | (get-all-text (:msg-info-obj (prettify-exception (import-from-file "exceptions/end_of_file.ser"))))) 108 | 109 | (expect #"Name splt does not exist in the namespace clojure\.string\.\nFound(.+)" 110 | (get-all-text (run-and-catch-pretty-no-stacktrace 'intro.core '(clojure.string/splt "pattern" #"/")))) 111 | 112 | (expect #"The namespace clojure does not exist or is not accessible in your program\.\n(.+)" 113 | (get-all-text (run-and-catch-pretty-no-stacktrace 'intro.core '(clojure/string/splt "pattern" #"/")))) 114 | 115 | (expect "There is an unmatched delimiter )." 116 | (get-text-no-location (:msg-info-obj (try (load-file "exceptions/compilation_errors/unmatched_delim_re.clj") 117 | (catch Throwable e (prettify-exception e)))))) 118 | 119 | (expect "An opened \" does not have a matching closing one." 120 | (get-text-no-location (:msg-info-obj (try (load-file "exceptions/compilation_errors/non_closing_string.clj") 121 | (catch Throwable e (prettify-exception e)))))) 122 | 123 | 124 | -------------------------------------------------------------------------------- /test/exception_msgs/overwritten_fns/str_fns.clj: -------------------------------------------------------------------------------- 1 | (ns exception_msgs.overwritten_fns.str_fns 2 | (:use [errors.prettify_exception :only [line-number-format]]) 3 | (:require [expectations :refer :all] 4 | [errors.messageobj :refer :all] 5 | [errors.testing_tools :refer :all] 6 | [utilities.file_IO :refer :all] 7 | )) 8 | 9 | ;; testing for :class-cast-exception 10 | (expect "Attempted to use a string, but a character was expected." 11 | (get-text-no-location 12 | (run-and-catch-pretty-no-stacktrace 'intro.core 13 | '(int "banana")))) 14 | 15 | ;; We use the same message as for identfier undefined, but have a hint that is 16 | ;; more specific to dynamic class loading 17 | (expect (more-> "Name clojure.string.split is undefined." get-text-no-location 18 | (re-pattern (str "(.*)Found in (.*)" (line-number-format "(\\d+)" "(\\d+)") "\\.")) get-all-text) 19 | (run-and-catch-pretty-no-stacktrace 'intro.core '(clojure.string.split "a b c" " "))) 20 | -------------------------------------------------------------------------------- /test/intro/student_test.clj: -------------------------------------------------------------------------------- 1 | (ns intro.student_test 2 | (:use [errors.prettify_exception :only [line-number-format]]) 3 | (:require [expectations :refer :all] 4 | [errors.messageobj :refer :all] 5 | [errors.testing_tools :refer :all] 6 | [utilities.stacktrace_functions :refer :all] 7 | [intro.student :refer :all])) 8 | 9 | (def location-regex (re-pattern (str "(.*)\\nFound in file student.clj" (line-number-format "(\\d+)" "(\\d+)") "(.*)"))) 10 | (def location-regex-core (re-pattern (str "(.*)\\nFound in file core.clj" (line-number-format "(\\d+)" "(\\d+)") "(.*)"))) 11 | (defn location-regex-exact 12 | "takes a line number and returns a regex pattern with the exact line number" 13 | [line-n] 14 | (re-pattern (str "(.*)\\nFound in file student.clj" (line-number-format line-n nil) "(.*)"))) 15 | 16 | ;; showing the resulting regex: 17 | (expect #"(.*)\nFound in file student.clj on, or before, line (\d+)(.*)" location-regex) 18 | 19 | ;; testing for exercise3 20 | (expect (more-of x 21 | java.lang.StringIndexOutOfBoundsException (:exception-class x) 22 | #"Position 12 is outside of the string\.(.*)" (get-all-text (:msg-info-obj x)) 23 | (location-regex-exact 60) (get-all-text (:msg-info-obj x)) ; use location-regex for generic line number testing 24 | (trace-has-all-pairs? {:fn "subs" :ns "clojure.core"}) (:stacktrace x) 25 | (trace-doesnt-have-pair? :ns "expectations") (:filtered-stacktrace x)) 26 | (run-and-catch-pretty-with-stacktrace 'intro.student 27 | '(exercise3 "helloworld" 5))) 28 | 29 | ;; testing for prob15 30 | (expect (more-of x 31 | java.lang.ClassCastException (:exception-class x) 32 | #"Attempted to use a number, but a function was expected\.(.*)" (get-all-text (:msg-info-obj x)) 33 | (location-regex-exact 66) (get-all-text (:msg-info-obj x)) 34 | (trace-has-all-pairs? {:fn "prob15" :ns "intro.student"}) (:stacktrace x)) 35 | (run-and-catch-pretty-with-stacktrace 'intro.student 36 | '(prob15 5))) 37 | 38 | ;; testing for prob16 39 | (expect (more-of x 40 | clojure.lang.ExceptionInfo (:exception-class x) 41 | #"You cannot pass three arguments to a function cons, need two\,\nin the function call \(cons \"Hello, \" \"Dave\" \"!\"\)" (get-all-text (:msg-info-obj x)) 42 | (location-regex-exact 73) (get-all-text (:msg-info-obj x)) 43 | (trace-has-all-pairs? {:fn "prob16" :ns "intro.student"}) (:stacktrace x)) 44 | (run-and-catch-pretty-with-stacktrace 'intro.student 45 | '(prob16 "Dave"))) 46 | 47 | ;; testing for prob17############ 48 | (expect (more-of x 49 | clojure.lang.ExceptionInfo (:exception-class x) 50 | #"In function \+, the second argument must be a number but is nil,\nin the function call \(\+ 5 nil\)\n(.*)" (get-all-text (:msg-info-obj x)) 51 | (location-regex-exact 84) (get-all-text (:msg-info-obj x)) 52 | (trace-has-all-pairs? {:fn "prob17" :ns "intro.student"}) (:stacktrace x)) 53 | (run-and-catch-pretty-with-stacktrace 'intro.student 54 | '(prob17 '(1 2 3)))) 55 | 56 | ;; testing for prob18######### 57 | (expect (more-of x 58 | clojure.lang.ExceptionInfo (:exception-class x) 59 | #"In function <, the first argument must be a number but is nil,\nin the function call \(< nil 5\)\n(.*)" (get-all-text (:msg-info-obj x)) 60 | (location-regex-exact 90) (get-all-text (:msg-info-obj x)) 61 | (trace-has-all-pairs? {:fn "prob18" :ns "intro.student"}) (:stacktrace x)) 62 | (run-and-catch-pretty-with-stacktrace 'intro.student 63 | '(prob18 '(3 4 5 6 7)))) 64 | 65 | ;; testing for prob64########## 66 | (expect (more-of x 67 | clojure.lang.ExceptionInfo (:exception-class x) 68 | #"In function \+, the second argument must be a number but is nil,\nin the function call \(\+ 15 nil\)\n(.*)" (get-all-text (:msg-info-obj x)) 69 | (location-regex-exact 103) (get-all-text (:msg-info-obj x)) 70 | (trace-has-all-pairs? {:fn "prob64" :ns "intro.student"}) (:stacktrace x)) 71 | (run-and-catch-pretty-with-stacktrace 'intro.student 72 | '(prob64 [1 2 3 4 5]))) 73 | 74 | ;; take-while now is allowed arity 1, as of Clojure 1.7 75 | ;; testing for prob57 76 | ;(expect (more-of x 77 | ; clojure.lang.ArityException (:exception-class x) 78 | ; "Wrong number of arguments (1) passed to a function take-while" (get-all-text (:msg-info-obj x)) 79 | ; (trace-has-all-pairs? {:fn "prob57" :ns "intro.student"}) (:stacktrace x)) 80 | ; (run-and-catch-pretty-with-stacktrace 'intro.student 81 | ; '(prob57 5))) 82 | 83 | ;; Elena, 6/17/16: this is actually not an error since a keyword is a function 84 | ;; testing for prob134 85 | ;(expect (more-of x 86 | ; java.lang.AssertionError (:exception-class x) 87 | ; #"In function filter, the first argument :a must be a function but is a keyword\.(.*)" (get-all-text (:msg-info-obj x)) 88 | ; location-regex (get-all-text (:msg-info-obj x)) 89 | ; (trace-has-all-pairs? {:fn "prob134" :ns "intro.student"}) (:filtered-stacktrace x)) 90 | ; (run-and-catch-pretty-with-stacktrace 'intro.student 91 | ; '(prob134 :a {:a nil :b 2}))) 92 | 93 | ;; Elena, 6/17/16: this is actually not an error since a map is a function 94 | ;; testing for prob156 95 | ;(expect (more-of x 96 | ; java.lang.AssertionError (:exception-class x) 97 | ; #"In function map, the first argument \{:a 0\} must be a function but is a map\.(.*)" (get-all-text (:msg-info-obj x)) 98 | ; location-regex (get-all-text (:msg-info-obj x)) 99 | ; (trace-has-all-pairs? {:fn "prob156" :ns "intro.student"}) (:filtered-stacktrace x)) 100 | ; (run-and-catch-pretty-with-stacktrace 'intro.student 101 | ; '(prob156 0 [:a :b :c]))) 102 | 103 | ;; testing for prob20 104 | (expect (more-of x 105 | clojure.lang.ExceptionInfo (:exception-class x) 106 | #"In function nth, the first argument 3 must be a sequence but is a number,\nin the function call \(nth 3 \[:a :b :c :d\]\)\n(.*)" (get-all-text (:msg-info-obj x)) 107 | (location-regex-exact 140) (get-all-text (:msg-info-obj x)) 108 | (trace-has-all-pairs? {:fn "prob20" :ns "intro.student"}) (:filtered-stacktrace x)) 109 | (run-and-catch-pretty-with-stacktrace 'intro.student 110 | '(prob20 [:a :b :c :d]))) 111 | 112 | ;; take now is allowed arity 1, as of Clojure 1.7 113 | ;; testing for prob21 114 | ;(expect (more-of x 115 | ; clojure.lang.ArityException (:exception-class x) 116 | ; "Wrong number of arguments (1) passed to a function take" (get-all-text (:msg-info-obj x)) 117 | ; (trace-has-all-pairs? {:fn "prob21" :ns "intro.student"}) (:filtered-stacktrace x)) 118 | ; (run-and-catch-pretty-with-stacktrace 'intro.student 119 | ; '(prob21 '(4 5 6 7) 2))) 120 | 121 | ;; testing for add-five 122 | (expect (more-of x 123 | clojure.lang.ExceptionInfo (:exception-class x) 124 | #"In function cons, the second argument 5 must be a sequence but is a number,\nin the function call \(cons 5 5\)\n(.*)" (get-all-text (:msg-info-obj x)) 125 | (location-regex-exact 153) (get-all-text (:msg-info-obj x)) 126 | (trace-has-all-pairs? {:fn "add-five" :ns "intro.student"}) (:filtered-stacktrace x)) 127 | (run-and-catch-pretty-with-stacktrace 'intro.student 128 | '(add-five 5))) 129 | 130 | ;; testing for prob24 131 | (expect (more-of x 132 | clojure.lang.ExceptionInfo (:exception-class x) 133 | #"In function \+, the second argument \(2 3\) must be a number but is a sequence,\nin the function call \(\+ 1 \(2 3\)\)\n(.*)" (get-all-text (:msg-info-obj x)) 134 | (location-regex-exact 160) (get-all-text (:msg-info-obj x)) 135 | (trace-has-all-pairs? {:fn "prob24" :ns "intro.student"}) (:filtered-stacktrace x)) 136 | (run-and-catch-pretty-with-stacktrace 'intro.student 137 | '(prob24 [1 2 3]))) 138 | 139 | ;; testing for prob23 140 | (expect (more-of x 141 | java.lang.IndexOutOfBoundsException (:exception-class x) 142 | #"An index in a sequence is out of bounds or invalid\.(.*)" (get-all-text (:msg-info-obj x)) 143 | (location-regex-exact 171) (get-all-text (:msg-info-obj x)) 144 | (trace-has-all-pairs? {:fn "prob23" :ns "intro.student"}) (:filtered-stacktrace x)) 145 | (run-and-catch-pretty-with-stacktrace 'intro.student 146 | '(prob23 [5 4 3 2 1]))) 147 | 148 | ;; testing for prob27 149 | (expect (more-of x 150 | clojure.lang.ArityException (:exception-class x) 151 | #"You cannot pass zero arguments to a function equal-to\?\.(.*)" (get-all-text (:msg-info-obj x)) 152 | (location-regex-exact 184) (get-all-text (:msg-info-obj x)) 153 | (trace-has-all-pairs? {:fn "prob27" :ns "intro.student"}) (:filtered-stacktrace x)) 154 | (run-and-catch-pretty-with-stacktrace 'intro.student 155 | '(prob27 '(\r \a \c \e \c \a \r)))) 156 | 157 | 158 | ;; testing for exercise9 159 | (expect (more-of x 160 | clojure.lang.ArityException (:exception-class x) 161 | #"You cannot pass one argument to this anonymous function\.(.*)" (get-all-text (:msg-info-obj x)) 162 | (location-regex-exact 194) (get-all-text (:msg-info-obj x)) 163 | (trace-has-all-pairs? {:fn "exercise9" :ns "intro.student"}) (:filtered-stacktrace x)) 164 | (run-and-catch-pretty-with-stacktrace 'intro.student 165 | '(exercise9))) 166 | 167 | ;; testing for prob38 168 | (expect (more-of x 169 | clojure.lang.ArityException (:exception-class x) 170 | ;;; This is a legitimate location error, need to look into it -> The line number seems to indicate this file line 174 171 | ;; location-regex (get-all-text (:msg-info-obj x)) 172 | #"You cannot pass four arguments to a function prob38\.(.*)" (get-all-text (:msg-info-obj x))) 173 | (run-and-catch-pretty-with-stacktrace 'intro.student 174 | '(prob38 1 8 3 4))) 175 | 176 | ;; testing for error-in-anonymous 177 | (expect (more-of x 178 | clojure.lang.ExceptionInfo (:exception-class x) 179 | #"In function \+, the first argument \"hi\" must be a number but is a string,\nin the function call \(\+ \"hi\" 2\)\n(.*)" (get-all-text (:msg-info-obj x)) 180 | ;;#"In function \+, the first argument \"hi\" must be a number but is a string,\nin the function call(.*)\n(.*)" (get-all-text (:msg-info-obj x)) 181 | location-regex (get-all-text (:msg-info-obj x)) 182 | (trace-has-all-pairs? {:fn "error-in-anonymous" :anon-fn true :ns "intro.student"}) (:filtered-stacktrace x) 183 | (trace-has-all-pairs? {:fn "error-in-anonymous" :anon-fn false :ns "intro.student"}) (:filtered-stacktrace x)) 184 | (run-and-catch-pretty-with-stacktrace 'intro.student 185 | '(error-in-anonymous))) 186 | 187 | ;; testing for error-in-map-inc 188 | (expect (more-of x 189 | java.lang.AssertionError (:exception-class x) 190 | #"In function inc, the argument \"hi\" must be a number but is a string\.(.*)" (get-all-text (:msg-info-obj x)) 191 | location-regex (get-all-text (:msg-info-obj x)) 192 | (trace-has-all-pairs? {:fn "error-in-map-inc" :anon-fn false :ns "intro.student"}) (:filtered-stacktrace x)) 193 | (run-and-catch-pretty-with-stacktrace 'intro.student 194 | '(error-in-map-inc))) 195 | -------------------------------------------------------------------------------- /test/location/compilation_errors.clj: -------------------------------------------------------------------------------- 1 | (ns location.compilation_errors 2 | (:use [errors.prettify_exception :only [line-number-format prettify-exception]]) 3 | (:require [expectations :refer :all] 4 | [errors.messageobj :refer :all] 5 | [errors.testing_tools :refer :all] 6 | [utilities.file_IO :refer :all] 7 | )) 8 | 9 | ;###################################### 10 | ;### Testing for compilation errors ### 11 | ;###################################### 12 | 13 | (expect (str "End of file, starting at line 3.\nProbably a non-closing parenthesis or bracket.\nFound in file eof.clj" (line-number-format 4 1) ".") 14 | (get-all-text (:msg-info-obj (prettify-exception (import-from-file "exceptions/end_of_file.ser"))))) 15 | 16 | (expect (str "There is an unmatched delimiter ).\nFound in file unmatched_delimiter.clj" (line-number-format 3 20) ".") 17 | (get-all-text (:msg-info-obj (prettify-exception (import-from-file "exceptions/unmatched_delimiter.ser"))))) 18 | 19 | ;########################################### 20 | ;### Testing for ClassNotFoundException #### 21 | ;########################################### 22 | (expect (more-> "Name clojure.string.split is undefined." get-text-no-location 23 | (re-pattern (str "(.*)Found in (.*)" (line-number-format "(\\d+)" "(\\d+)") "\\.")) get-all-text) 24 | (run-and-catch-pretty-no-stacktrace 'intro.core '(clojure.string.split "a b c" " "))) 25 | 26 | 27 | ;; testing for :illegal-argument-exactly-2-forms 28 | (expect #"The function when-let requires exactly 2 forms in binding vector. Line (.*) in the file intro.core" 29 | (get-all-text (run-and-catch-pretty-no-stacktrace 'intro.core '(when-let [num1 1 num2 2] "hello")))) 30 | -------------------------------------------------------------------------------- /test/location/overwritten_fns.clj: -------------------------------------------------------------------------------- 1 | (ns location.overwritten_fns 2 | (:use [errors.prettify_exception :only [line-number-format]]) 3 | (:require [expectations :refer :all] 4 | [errors.messageobj :refer :all] 5 | [errors.testing_tools :refer :all] 6 | [errors.prettify_exception :refer :all] 7 | [utilities.file_IO :refer :all] 8 | )) 9 | -------------------------------------------------------------------------------- /test/location/runtime_errors.clj: -------------------------------------------------------------------------------- 1 | (ns location.runtime_errors 2 | (:use [errors.prettify_exception :only [line-number-format]]) 3 | (:require [expectations :refer :all] 4 | [errors.messageobj :refer :all] 5 | [errors.testing_tools :refer :all] 6 | [errors.prettify_exception :refer :all] 7 | [utilities.file_IO :refer :all] 8 | )) 9 | -------------------------------------------------------------------------------- /test/spec_behavior/spec_test.clj: -------------------------------------------------------------------------------- 1 | (ns spec_behavior.spec_test 2 | (:require [expectations :refer :all] 3 | [corefns.corefns :refer :all])) 4 | 5 | ;############################################ 6 | ;### Tests for spec data in ExceptionInfo ### 7 | ;### ### 8 | ;############################################ 9 | 10 | (defn hashmap-gen 11 | "takes a quoted function and an optional path to the acutal error call, 12 | and returns an auto-generated spec hash-map when an error happens. 13 | It gets the first hashmap in the resulting vector without the n-th argument. 14 | Use init-hash-gen function to see how an initial hashmap looks like" 15 | ([f] 16 | (try (eval f) 17 | (catch clojure.lang.ExceptionInfo e 18 | (clojure.core/nth (:clojure.spec/problems (.getData e)) 0)))) 19 | ([f n-th] 20 | (try (eval f) 21 | (catch clojure.lang.ExceptionInfo e 22 | (clojure.core/nth (:clojure.spec/problems (.getData e)) n-th))))) 23 | 24 | (defn init-hashmap-gen 25 | "takes a quoted function call and returns the error data as a hashmap 26 | when an error happens" 27 | [f] 28 | (try (eval f) 29 | (catch clojure.lang.ExceptionInfo e 30 | (.getData e)))) 31 | 32 | ; 0 failures 0 errors 33 | ; An example error data: 34 | ;{:clojure.spec/problems [{:path [:args], :pred length1?, :val ([] []), :via [:spec-alpha.specs/length-one], :in []}], ...} 35 | 36 | ;; ########## tests for empty? ########## 37 | (expect false (empty? [1 2])) 38 | (expect true (empty? nil)) 39 | ; We need ' (quote) for the :pred value 40 | (expect {:path [:args :check-seqable], :pred 'seqable?, :val 3, :via [], :in [0]} 41 | (hashmap-gen '(empty? 3))) 42 | ; The value of key :pred is a lazy sequence 43 | ;; (expect (more-> 'cat first 44 | ;; :check-seqable second 45 | ;; 'seqable? last) 46 | ;; (:pred (hashmap-gen '(empty? [] [])))) 47 | (expect {:path [:args], :pred 'length1?, :val '([] []), :via [:corefns.specs/length-one], :in []} 48 | (hashmap-gen '(empty? [] []))) 49 | (expect {:path [:args], :pred 'length1?, :val 'nil, :via [:corefns.specs/length-one], :in []} 50 | (hashmap-gen '(empty?))) 51 | (expect {:path [:args :check-seqable], :pred 'seqable?, :val true, :via [], :in [0]} 52 | (hashmap-gen '(empty? true))) 53 | 54 | 55 | ;; ########## tests for map ########## 56 | (expect [2 3 4] (map inc [1 2 3])) 57 | (expect {:path [:args], :pred 'length-greater1?, :val '(10), :via [:corefns.specs/length-greater-one], :in []} 58 | (hashmap-gen '(map 10))) 59 | (expect {:path [:args :check-function], :pred 'ifn?, :val 1, :via [], :in [0]} 60 | (hashmap-gen '(map 1 [1 3 4] [1 2 3] [1 3 4]))) 61 | (expect {:path [:args :check-seqable] :pred 'seqable?, :val 3, :via [], :in [4]} 62 | (hashmap-gen '(map empty? [1 2 3] [2 3 4] [3 4 5] 3))) 63 | 64 | 65 | ;; ########## tests for conj ########## 66 | (expect [1 2 3 3 [2]] (conj [1 2] 3 3 [2])) 67 | (expect {:path [:args], :pred 'length-greater1?, :val '([1]), :via [:corefns.specs/length-greater-one], :in []} 68 | (hashmap-gen '(conj [1]))) 69 | (expect {:path [:args :check-seqable], :pred 'seqable?, :val 1, :via [], :in [0]} 70 | (hashmap-gen '(conj 1 3))) 71 | (expect {:path [:args :check-seqable], :pred 'seqable?, :val true, :via [], :in [0]} 72 | (hashmap-gen '(conj true 3))) 73 | 74 | 75 | ;; ########## tests for reduce ########## 76 | (expect 6 (reduce + [1 2 3])) 77 | (expect 16 (reduce + 10 [1 2 3])) 78 | (expect {:path [:args :two-case :check-function], :pred 'ifn?, :val 1, :via [], :in [0]} 79 | (hashmap-gen '(reduce 1 [1 2 3]) 0)) 80 | (expect {:path [:args :three-case], :pred 'length3?, :val '(1 [1 2 3]), :via [:corefns.specs/length-three], :in []} 81 | (hashmap-gen '(reduce 1 [1 2 3]) 1)) 82 | (expect {:path [:args :two-case], :pred 'length2?, :val '([1 2] 0 [] 1), :via [:corefns.specs/length-two], :in []} 83 | (hashmap-gen '(reduce [1 2] 0 [] 1) 0)) 84 | (expect {:path [:args :three-case], :pred 'length3?, :val '([1 2] 0 [] 1), :via [:corefns.specs/length-three], :in []} 85 | (hashmap-gen '(reduce [1 2] 0 [] 1) 1)) 86 | (expect {:path [:args :two-case], :pred 'length2?, :val '(1), :via [:corefns.specs/length-two], :in []} 87 | (hashmap-gen '(reduce 1) 0)) 88 | (expect {:path [:args :three-case], :pred 'length3?, :val '(1), :via [:corefns.specs/length-three], :in []} 89 | (hashmap-gen '(reduce 1) 1)) 90 | (expect {:path [:args :two-case :check-seqable], :pred 'seqable?, :val 3, :via [], :in [1]} 91 | (hashmap-gen '(reduce + 3) 0)) 92 | (expect {:path [:args :three-case :check-seqable], :pred 'seqable?, :val 2, :via [], :in [2]} 93 | (hashmap-gen '(reduce + 2 2) 1)) 94 | 95 | 96 | ;; ########## tests for nth ############ 97 | ; using a helper function and late eval doesn't work for some reason. 98 | ; need to use try-catch directly 99 | (expect "lazy" (nth ["lazy" "person" "you"] 0)) 100 | (expect "working" (nth ["lazy" "person" "you"] 4 "working")) 101 | (expect {:path [:args :two-case :check-seqable] :pred 'seqable?, :val 1, :via [], :in [0]} 102 | (try (nth 1 "") 103 | (catch clojure.lang.ExceptionInfo e 104 | (clojure.core/nth (:clojure.spec/problems (.getData e)) 0)))) 105 | (expect {:path [:args :three-case] :pred 'length3?, :val '(1 ""), :via [:corefns.specs/length-three], :in []} 106 | (try (nth 1 "") 107 | (catch clojure.lang.ExceptionInfo e 108 | (clojure.core/nth (:clojure.spec/problems (.getData e)) 1)))) 109 | (expect {:path [:args :two-case] :pred 'length2?, :val '([1 2 3] [1] "Boo"), :via [:corefns.specs/length-two], :in []} 110 | (try (nth [1 2 3] [1] "Boo") 111 | (catch clojure.lang.ExceptionInfo e 112 | (clojure.core/nth (:clojure.spec/problems (.getData e)) 0)))) 113 | (expect {:path [:args :three-case :check-number] :pred 'number?, :val [1], :via [], :in [1]} 114 | (try (nth [1 2 3] [1] "Boo") 115 | (catch clojure.lang.ExceptionInfo e 116 | (clojure.core/nth (:clojure.spec/problems (.getData e)) 1)))) 117 | 118 | 119 | ;; ########## tests for assoc ########## 120 | (expect {:key1 "value", :key2 "another value"} (assoc {} :key1 "value" :key2 "another value")) 121 | (expect [1 nil] (assoc [1 2] 1 nil)) 122 | (expect {nil nil} (assoc {} nil nil)) 123 | (expect {:path [:args], :pred 'length-greater2?, :val '([1 2]), :via [:corefns.specs/length-greater-two], :in []} 124 | (hashmap-gen '(assoc [1 2]))) 125 | (expect {:path [:args], :pred 'length-greater2?, :val '({} :random), :via [:corefns.specs/length-greater-two], :in []} 126 | (hashmap-gen '(assoc {} :random))) 127 | (expect {:path [:args :check-map-or-vector :check-map :clojure.spec/nil], :pred 'nil?, :val '(), :via [], :in [0]} 128 | (hashmap-gen '(assoc '() nil nil))) 129 | (expect {:path [:args :check-map-or-vector :check-map :clojure.spec/pred], :pred 'map?, :val '(), :via [], :in [0]} 130 | (hashmap-gen '(assoc '() nil nil) 1)) 131 | (expect {:path [:args :check-map-or-vector :check-vector], :pred 'vector?, :val '(), :via [], :in [0]} 132 | (hashmap-gen '(assoc '() nil nil) 2)) 133 | 134 | 135 | ;; ########## tests for dissoc ########## 136 | (expect {:c 3} (dissoc {:a 1 :b 2 :c 3} :a :b)) 137 | (expect {:path [:args], :pred 'length-greater0?, :val nil, :via [:corefns.specs/length-greater-zero], :in []} 138 | (hashmap-gen '(dissoc))) 139 | (expect {:path [:args :check-map :clojure.spec/nil], :pred 'nil?, :val [0], :via [], :in [0]} 140 | (hashmap-gen '(dissoc [0]))) 141 | (expect {:path [:args :check-map :clojure.spec/pred], :pred 'map?, :val [0], :via [], :in [0]} 142 | (hashmap-gen '(dissoc [0]) 1)) 143 | 144 | 145 | ;; ########## tests for quot ########### 146 | ; using a helper function and late eval doesn't work for some reason. 147 | ; need to use try-catch directly 148 | (expect 3 (quot 12 4)) 149 | (expect 3 (quot 9 3)) 150 | (expect {:path [:args :check-number], :pred 'number?, :val false, :via [], :in [0]} 151 | (try (quot false 10) 152 | (catch clojure.lang.ExceptionInfo e 153 | (clojure.core/nth (:clojure.spec/problems (.getData e)) 0)))) 154 | (expect {:path [:args], :pred 'length2?, :val '(30 "" true), :via [::corefns.specs/length-two], :in []} 155 | (try (quot 30 "" true) 156 | (catch clojure.lang.ExceptionInfo e 157 | (clojure.core/nth (:clojure.spec/problems (.getData e)) 0)))) 158 | 159 | 160 | ;; ########## tests for repeat ########### 161 | (expect '("x" "x" "x" "x" "x") (take 5 (repeat "x"))) 162 | (expect '("x" "x" "x" "x" "x") (repeat 5 "x")) 163 | (expect {:path [:args :one-case], :pred 'length1?, :val '("" 5), :via [:corefns.specs/length-one], :in []} 164 | (hashmap-gen '(repeat "" 5))) 165 | (expect {:path [:args :two-case :check-number], :pred 'number?, :val "", :via [], :in [0]} 166 | (hashmap-gen '(repeat "" 5) 1)) 167 | (expect {:path [:args :one-case], :pred 'length1?, :val '(5 :some :any 100.0), :via [:corefns.specs/length-one], :in []} 168 | (hashmap-gen '(repeat 5 :some :any 100.0))) 169 | (expect {:path [:args :two-case], :pred 'length2?, :val '(5 :some :any 100.0), :via [:corefns.specs/length-two], :in []} 170 | (hashmap-gen '(repeat 5 :some :any 100.0) 1)) 171 | -------------------------------------------------------------------------------- /test/stacktrace_testing/stacktrace_testing.clj: -------------------------------------------------------------------------------- 1 | (ns stacktrace_testing.stacktrace_testing 2 | (:require [expectations :refer :all] 3 | [errors.messageobj :refer :all] 4 | [clj-stacktrace.core :as stacktrace] 5 | [utilities.stacktrace_functions :refer :all] 6 | [errors.testing_tools :refer :all] 7 | [errors.prettify_exception :refer :all] 8 | [corefns.collection_fns :refer :all] 9 | [errors.testing_tools :refer :all] 10 | [utilities.file_IO :refer :all] 11 | ) 12 | (:import [java.io.FileInputStream] 13 | [java.io.ObjectInputStream] 14 | [java.io.FileOutputStream] 15 | [java.io.ObjectOutputStream] 16 | [java.util.ArrayList])) 17 | 18 | 19 | 20 | ;################################## 21 | ;## 4. More real-life exceptions ## 22 | ;################################## 23 | 24 | 25 | ;;; We actually might want to change this exception by adding or changing a pre-condition for conj 26 | 27 | (def dr-racket-exercise-class-cast (import-from-file "exceptions/DrRacket-Exercise2-ClassCast.ser")) 28 | 29 | (def prettified-class-cast (prettify-exception dr-racket-exercise-class-cast)) 30 | 31 | (expect ClassCastException (:exception-class prettified-class-cast)) 32 | 33 | (expect (trace-has-pair? :fn "exercise2") (:stacktrace prettified-class-cast)) 34 | 35 | (expect (trace-has-pair? :fn "exercise2") (:filtered-stacktrace prettified-class-cast)) 36 | 37 | (expect (trace-has-all-pairs? {:fn "exercise2" :ns "intro.student" :file "student.clj" :line 50}) 38 | (:filtered-stacktrace prettified-class-cast)) 39 | 40 | (expect (trace-has-all-pairs? {:fn "-main" :ns "intro.core" :file "core.clj"}) 41 | (:filtered-stacktrace prettified-class-cast)) 42 | 43 | (expect (trace-doesnt-have-all-pairs? {:ns "core.main"}) prettified-class-cast) 44 | 45 | (expect (check-stack-count? 6) (:filtered-stacktrace prettified-class-cast)) 46 | 47 | (expect (trace-has-all-pairs? {:fn "apply" :ns "clojure.core"}) (:filtered-stacktrace prettified-class-cast)) 48 | 49 | (expect (top-elem-has-all-pairs? {:fn "conj" :ns "clojure.core"}) (:filtered-stacktrace prettified-class-cast)) 50 | 51 | (expect (trace-has-all-pairs? {:fn "conj" :ns "corefns.corefns"}) (:filtered-stacktrace prettified-class-cast)) 52 | 53 | ;############################################## 54 | ;## 5. Comparing top elements of stacktraces ## 55 | ;############################################## 56 | 57 | ;##################### 58 | ;### 5.1 functions ### 59 | ;##################### 60 | 61 | (defn important-part? 62 | "Takes a vector of a key-value pair and then returns true if the key matches any key in the vector of keys" 63 | [pair] 64 | (any? true? (map #(= (first pair) %) [:method :class :java :fn :ns :clojure]))) 65 | 66 | (defn get-important-in-stacktrace 67 | "Takes a stacktrace (a vector of hashmaps), and then only keeps what returns true from important-part?" 68 | [stacktrace] 69 | (vec (map #(into {} (filter important-part? %)) stacktrace))) 70 | 71 | (defn make-trace-comparison 72 | "Takes an exception and a piece of quoted code or a string filename, makes an exception, and then returns a hashmap consisting 73 | of the filename/quoted code, a boolean stating whether the top elements of both the filtered and unfiltered stacktraces match, 74 | and then the unfiltered stacktrace up until the first match of the top element of the filtered stacktrace to the unfiltered, 75 | excluding unimportant parts, and finally the top element of the filtered, excluding unimportant parts." 76 | [exception source] 77 | (let [prettified-exception (prettify-exception exception) 78 | stacktrace (:stacktrace prettified-exception) 79 | top-of-filtered-stacktrace (first (:filtered-stacktrace prettified-exception)) 80 | beginning-of-stacktrace (take (inc (.indexOf stacktrace top-of-filtered-stacktrace)) stacktrace)] 81 | {:source source 82 | :top-elements-match? (= (first beginning-of-stacktrace) top-of-filtered-stacktrace) 83 | :beginning-of-unfiltered-trace (get-important-in-stacktrace beginning-of-stacktrace) 84 | :top-element-of-filtered-trace (into {} (filter important-part? top-of-filtered-stacktrace))})) 85 | 86 | (defn format-and-print-comparison 87 | "Takes a result of make-trace-comparison and will print everything out prettily in the terminal, with new lines and tabs" 88 | [trace-comparison] 89 | (do 90 | (println "source: "(:source trace-comparison) "\n" 91 | "top elements match?: "(:top-elements-match? trace-comparison) "\n" 92 | "beginning of unfiltered trace:" (interpose "\n \t \t \t \t" (:beginning-of-unfiltered-trace trace-comparison)) "\n" 93 | "top element of filtered trace: " (:top-element-of-filtered-trace trace-comparison) "\n") 94 | true)) 95 | 96 | ;; To print comparisons, uncomment the doall line. 97 | (defn format-and-print-comparisons 98 | "Takes in a list of hashmaps (results of make-trace-comparison), and calls a function to print everything and returns true. 99 | Uncomment the third line here if you want to see a trace-comparison of stacktrace tops." 100 | [trace-comparisons] 101 | (do 102 | ;(doall (map format-and-print-comparison trace-comparisons)) 103 | true)) 104 | 105 | (defn make-and-print-comparisons 106 | "Takes in a collection of exceptions and a collection of either strings or quoted code, and then generates a trace-comparison 107 | for them, and then prints them nicely in the terminal." 108 | [coll-of-exceptions coll-of-sources] 109 | (format-and-print-comparisons (map make-trace-comparison coll-of-exceptions coll-of-sources))) 110 | 111 | (defn compare-traces-of-quoted-code 112 | "Takes a namespace, and an unlimited number of pieces of quoted code, and then turns the quoted code into an exceptions, 113 | and then calls make-and-print-comparisons to print trace-comparisons." 114 | [name-space & quoted-code] 115 | (make-and-print-comparisons (map #(run-and-catch-raw name-space %) quoted-code) quoted-code)) 116 | 117 | (defn compare-traces-of-saved-exceptions 118 | "Takes an unlimited number of filenames, and then calls make-and-print-comparisons on the saved exceptions to print the trace-comparisons." 119 | [& filenames] 120 | (make-and-print-comparisons (map #(import-from-file (str "exceptions/" %)) filenames) filenames)) 121 | 122 | (defn print-n-elements-of-stacktrace 123 | "Takes a number n and a stacktrace (filtered or unfiltered), and then prints that many elements of the stacktrace" 124 | [n trace] 125 | (do 126 | (println "\n First" n "elements of the stacktrace: \n"(interpose "\n" (take n trace))) 127 | true)) 128 | 129 | ;################## 130 | ;### 5.2 prints ### 131 | ;################## 132 | 133 | ;; to actually print the result in the terminal, uncomment the line in format-and-print-comparisons 134 | 135 | ;; saved-exceptions is actually at the top of this file 136 | 137 | (expect true (apply compare-traces-of-saved-exceptions saved-exceptions)) 138 | 139 | (expect true (compare-traces-of-quoted-code 'intro.student 140 | '(+ 2 "string") 141 | '(cons 1 2) 142 | '(inc "apple") 143 | '(prob16 "Dave") 144 | '(exercise2 "hello " "world") 145 | '(exercise9) 146 | '(error-in-anonymous))) 147 | 148 | ;(expect true (print-n-elements-of-stacktrace 100 (:stacktrace (run-and-catch-pretty-with-stacktrace 149 | ; 'intro.student 150 | ; '(error-in-map-inc))))) 151 | 152 | ;(expect true (print-n-elements-of-stacktrace 100 (:stacktrace (run-and-catch-pretty-with-stacktrace 153 | ; 'intro.student 154 | ; '(error-in-anonymous))))) 155 | 156 | (expect true (compare-traces-of-quoted-code 'intro.student 157 | '(cons 16 79))) 158 | 159 | (expect true (compare-traces-of-saved-exceptions "4clojure-prob156-AssertionError.ser")) 160 | 161 | ;################# 162 | ;### 5.3 tests ### 163 | ;################# 164 | 165 | ;; testing for make-trace-comparison 166 | ;; Elena: this used to be a test for non-matching top of teh stack, but now that we 167 | ;; have overwritten cons this is no longer valid. Commenting out. 168 | ;(expect {:source '(cons 16 79) 169 | ; :top-elements-match? false 170 | ; :beginning-of-unfiltered-trace '({:method "seqFrom", :class "clojure.lang.RT", :java true} 171 | ; {:method "seq", :class "clojure.lang.RT", :java true} 172 | ; {:method "cons", :class "clojure.lang.RT", :java true} 173 | ; {:fn "cons", :ns "clojure.core", :clojure true}) 174 | ; :top-element-of-filtered-trace {:fn "cons", :ns "clojure.core", :clojure true}} 175 | ; (make-trace-comparison (run-and-catch-raw 'intro.student 176 | ; '(cons 16 79)) '(cons 16 79))) 177 | 178 | (expect {:source "4clojure-prob156-AssertionError.ser" 179 | :top-elements-match? true 180 | :beginning-of-unfiltered-trace '({:fn "map", :ns "corefns.corefns", :clojure true}) 181 | :top-element-of-filtered-trace {:fn "map", :ns "corefns.corefns", :clojure true}} 182 | (make-trace-comparison (import-from-file "exceptions/4clojure-prob156-AssertionError.ser") "4clojure-prob156-AssertionError.ser")) 183 | -------------------------------------------------------------------------------- /test/utilities/file_IO.clj: -------------------------------------------------------------------------------- 1 | (ns utilities.file_IO 2 | (:require [expectations :refer :all] 3 | ;[clj-stacktrace.core :as stacktrace] 4 | ;[errors.prettify_exception :refer :all] 5 | ;[errors.messageobj :refer :all] 6 | ;[utilities.stacktrace_functions :refer :all] 7 | ;[corefns.collection_fns :refer :all] 8 | ) 9 | (:import [java.io.FileInputStream] 10 | [java.io.ObjectInputStream] 11 | [java.io.FileOutputStream] 12 | [java.io.ObjectOutputStream] 13 | [java.util.ArrayList])) 14 | 15 | ;################################ 16 | ;## Writing/Reading to file ## 17 | ;################################ 18 | 19 | ;## global vars ## 20 | ;(def path "exceptions/") 21 | 22 | ; 1.1 functions 23 | 24 | ;## NOTE ## 25 | ;;The second part of let is a series of expressions that evaluate in /order/, returning the last expression. 26 | ;;This is Important: 27 | ;;The following two functions are dependent on events occurring in chronological order. 28 | 29 | (defn export-to-file 30 | "Uses Java's Serializable to write a (java) object to a file" 31 | [obj filepath] 32 | (let [file-stream (java.io.FileOutputStream. filepath) 33 | obj-stream (java.io.ObjectOutputStream. file-stream) 34 | ] 35 | (.writeObject obj-stream obj) 36 | (.close obj-stream) 37 | (.close file-stream) 38 | ;(println (str "data saved in project folder or: " filepath)) 39 | )) 40 | 41 | (defn import-from-file 42 | "Uses Java's Serializable to read a (java) object from a file" 43 | [filepath] 44 | (let [file-stream (java.io.FileInputStream. filepath) 45 | obj-stream (java.io.ObjectInputStream. file-stream) 46 | e (.readObject obj-stream)] 47 | (.close obj-stream) 48 | (.close file-stream) 49 | e)) 50 | 51 | ; 1.2 testing reading/writing to file 52 | 53 | (def java-arraylist (new java.util.ArrayList 5)) 54 | 55 | (expect (.equals java-arraylist 56 | (let [filename "testfile.silly" 57 | object java-arraylist] 58 | (export-to-file object (str "exceptions/" filename)) 59 | (import-from-file (str "exceptions/" filename))))) 60 | -------------------------------------------------------------------------------- /test/utilities/stacktrace_functions.clj: -------------------------------------------------------------------------------- 1 | (ns utilities.stacktrace_functions 2 | (:require [expectations :refer :all] 3 | [corefns.collection_fns :refer :all])) 4 | 5 | ;; a helper function to test for either nil or false 6 | (defn not-true? [x] (not (true? x))) 7 | 8 | ;############################### 9 | ;### Checks a single element ### 10 | ;############################### 11 | 12 | ;### Item exists in trace elem ### 13 | 14 | (defn helper-trace-elem-has-function? [fun trace-elem] 15 | (= fun (:fn trace-elem))) 16 | 17 | (defn trace-elem-has-function? [fun] 18 | (partial helper-trace-elem-has-function? fun)) 19 | 20 | ;~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 21 | 22 | (defn helper-trace-elem-has-namespace? [name-space trace-elem] 23 | (= name-space (:ns trace-elem))) 24 | 25 | (defn trace-elem-has-namespace? [name-space] 26 | (partial helper-trace-elem-has-namespace? name-space)) 27 | 28 | ;~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 29 | 30 | (defn helper-trace-elem-has-pair? [k v trace-elem] 31 | (= v (k trace-elem))) 32 | 33 | (defn trace-elem-has-pair? [k v] 34 | (partial helper-trace-elem-has-pair? k v)) 35 | 36 | ;~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 37 | 38 | (defn helper-trace-elem-has-all-pairs? [kv-pairs trace-elem] 39 | "checks that every binding in kv-pairs also appears in trace-elem" 40 | (every? true? (map #(helper-trace-elem-has-pair? (first %) (second %) trace-elem) kv-pairs))) 41 | 42 | (defn trace-elem-has-all-pairs? [kv-pairs] 43 | (partial helper-trace-elem-has-all-pairs? kv-pairs)) 44 | 45 | ;### Item doesn't exists in trace elem ### 46 | 47 | (defn helper-trace-elem-doesnt-have-function? [fun trace-elem] 48 | (not (helper-trace-elem-has-function? fun trace-elem))) 49 | 50 | (defn trace-elem-doesnt-have-function? [fun] 51 | (partial helper-trace-elem-doesnt-have-function? fun)) 52 | 53 | ;~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 54 | 55 | (defn helper-trace-elem-doesnt-have-namespace? [name-space trace-elem] 56 | (not (helper-trace-elem-has-namespace? name-space trace-elem))) 57 | 58 | (defn trace-elem-doesnt-have-namespace? [name-space] 59 | (partial helper-trace-elem-doesnt-have-namespace? name-space)) 60 | 61 | ;~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 62 | 63 | (defn helper-trace-elem-doesnt-have-pair? [k v trace-elem] 64 | (not (helper-trace-elem-has-pair? k v trace-elem))) 65 | 66 | (defn trace-elem-doesnt-have-pair? [k v] 67 | (partial helper-trace-elem-doesnt-have-pair? k v)) 68 | 69 | ;~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 70 | (defn helper-trace-elem-doesnt-have-all-pairs? [kv-pairs trace-elem] 71 | "checks that every binding in kv-pairs also appears in trace-elem" 72 | (not (helper-trace-elem-has-all-pairs? kv-pairs trace-elem))) 73 | 74 | (defn trace-elem-doesnt-have-all-pairs? [kv-pairs] 75 | (partial helper-trace-elem-doesnt-have-all-pairs? kv-pairs)) 76 | 77 | ;################################# 78 | ;### Checks a whole stacktrace ### 79 | ;################################# 80 | 81 | ;; a helper function to test the size of a stacktrace 82 | (defn helper-stack-count? [n stacktrace] 83 | (= n (count stacktrace))) 84 | 85 | (defn check-stack-count? [n] 86 | (partial helper-stack-count? n)) 87 | 88 | ;### Item exists in stacktrace ### 89 | 90 | (defn helper-trace-has-function? [fun trace] 91 | (any? (trace-elem-has-function? fun) trace)) 92 | 93 | (defn trace-has-function? [fun] 94 | (partial helper-trace-has-function? fun)) 95 | 96 | ;~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 97 | 98 | (defn helper-trace-has-namespace? [name-space trace] 99 | (any? (trace-elem-has-namespace? name-space) trace)) 100 | 101 | (defn trace-has-namespace? [name-space] 102 | (partial helper-trace-has-namespace? name-space)) 103 | 104 | ;~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 105 | 106 | (defn helper-trace-has-pair? [k v trace] 107 | (any? (trace-elem-has-pair? k v) trace)) 108 | 109 | (defn trace-has-pair? [k v] 110 | (partial helper-trace-has-pair? k v)) 111 | 112 | ;~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 113 | 114 | (defn helper-trace-has-all-pairs? [kv-pairs trace] 115 | (any? (trace-elem-has-all-pairs? kv-pairs) trace)) 116 | 117 | (defn trace-has-all-pairs? [kv-pairs] 118 | (partial helper-trace-has-all-pairs? kv-pairs)) 119 | 120 | ;~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 121 | 122 | (defn helper-top-elem-has-pair? [k v trace] 123 | (helper-trace-elem-has-pair? k v (first trace))) 124 | 125 | (defn top-elem-has-pair? [k v] 126 | (partial helper-top-elem-has-pair? k v)) 127 | 128 | ;~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 129 | 130 | (defn helper-top-elem-has-all-pairs? [kv-pairs trace] 131 | (helper-trace-elem-has-all-pairs? kv-pairs (first trace))) 132 | 133 | (defn top-elem-has-all-pairs? [kv-pairs] 134 | (partial helper-top-elem-has-all-pairs? kv-pairs)) 135 | 136 | ;~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 137 | 138 | (defn helper-nth-elem-has-pair? [n k v trace] 139 | (helper-trace-elem-has-pair? k v (nth trace n))) 140 | 141 | (defn nth-elem-has-pair? [n k v] 142 | (partial helper-nth-elem-has-pair? n k v)) 143 | 144 | ;~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 145 | 146 | (defn helper-nth-elem-has-all-pairs? [n kv-pairs trace] 147 | (helper-trace-elem-has-all-pairs? kv-pairs (nth trace n))) 148 | 149 | (defn nth-elem-has-all-pairs? [n kv-pairs] 150 | (partial helper-nth-elem-has-all-pairs? n kv-pairs)) 151 | 152 | ;### Item doesn't exists in stacktrace ### 153 | 154 | (defn helper-trace-doesnt-have-function? [fun trace] 155 | (not (helper-trace-has-function? fun trace))) 156 | 157 | (defn trace-doesnt-have-function? [fun] 158 | (partial helper-trace-doesnt-have-function? fun)) 159 | 160 | ;~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 161 | 162 | (defn helper-trace-doesnt-have-namespace? [name-space trace] 163 | (not (helper-trace-has-namespace? name-space trace))) 164 | 165 | (defn trace-doesnt-have-namespace? [name-space] 166 | (partial helper-trace-doesnt-have-namespace? name-space)) 167 | 168 | ;~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 169 | 170 | (defn helper-trace-doesnt-have-pair? [k v trace] 171 | (not (helper-trace-has-pair? k v trace))) 172 | 173 | (defn trace-doesnt-have-pair? [k v] 174 | (partial helper-trace-doesnt-have-pair? k v)) 175 | 176 | ;~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 177 | 178 | (defn helper-trace-doesnt-have-all-pairs? [kv-pairs trace] 179 | (not (helper-trace-has-all-pairs? kv-pairs trace))) 180 | 181 | (defn trace-doesnt-have-all-pairs? [kv-pairs] 182 | (partial helper-trace-doesnt-have-all-pairs? kv-pairs)) 183 | 184 | ;~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 185 | --------------------------------------------------------------------------------