diff --git a/.dir-locals.el b/.dir-locals.el index ef2abb2e2b4a140bbaac66072a6da7237e5f7fff..6ef64b82e46d455fcad94a03c163a4e82d3e35d3 100644 --- a/.dir-locals.el +++ b/.dir-locals.el @@ -19,6 +19,7 @@ (expect-eval-actual-first 1) (expect-expansion 0) (expect-let 1) + (expect-when-testing-against-dataset 1) (expect-with-all-drivers 1) (ins 1) (let-400 1) @@ -33,4 +34,5 @@ (symbol-macrolet 1) (sync-in-context 2) (upd 2) + (when-testing-dataset 1) (with-credentials 1))))))) diff --git a/src/metabase/driver.clj b/src/metabase/driver.clj index c309a516cdd9ddddbc06880027081f6057229e15..fe7ab054d863333d01dbccdeebac7867f29ebca5 100644 --- a/src/metabase/driver.clj +++ b/src/metabase/driver.clj @@ -124,12 +124,17 @@ "Process a structured or native query, and return the result." [query] {:pre [(map? query)]} - (binding [qp/*query* query] - (let [driver (database-id->driver (:database query)) - query (qp/preprocess query) - results (binding [qp/*query* query] - (i/process-query driver (dissoc-in query [:query :cum_sum])))] ; strip out things that individual impls don't need to know about / deal with - (qp/post-process driver query results)))) + (try + (binding [qp/*query* query] + (let [driver (database-id->driver (:database query)) + query (qp/preprocess query) + results (binding [qp/*query* query] + (i/process-query driver (dissoc-in query [:query :cum_sum])))] ; strip out things that individual impls don't need to know about / deal with + (qp/post-process driver query results))) + (catch Throwable e + (.printStackTrace e) + {:status :failed + :error (.getMessage e)}))) ;; ## Query Execution Stuff diff --git a/src/metabase/driver/generic_sql/native.clj b/src/metabase/driver/generic_sql/native.clj index 38f72290ea09e7f612feca750424d01dbf502e9d..3e5bbf35c692bbdce8294085970566528c613043 100644 --- a/src/metabase/driver/generic_sql/native.clj +++ b/src/metabase/driver/generic_sql/native.clj @@ -51,21 +51,14 @@ (log/debug "Setting timezone to:" timezone) (jdbc/db-do-prepared conn set-timezone-sql))) (jdbc/query conn sql :as-arrays? true))] - {:status :completed - :row_count (count rows) - :data {:rows rows - :columns columns - :cols (map (fn [column first-value] - {:name column - :base_type (value->base-type first-value)}) - columns first-row)}}) + {:rows rows + :columns columns + :cols (map (fn [column first-value] + {:name column + :base_type (value->base-type first-value)}) + columns first-row)}) (catch java.sql.SQLException e - {:status :failed - :error (or (->> (.getMessage e) ; error message comes back like 'Column "ZID" not found; SQL statement: ... [error-code]' sometimes - (re-find #"^(.*);") ; the user already knows the SQL, and error code is meaningless - second) ; so just return the part of the exception that is relevant - (.getMessage e))}))) - -(def db (delay (-> (sel :one Database :id 1) - db->korma-db - korma.db/get-connection))) + (throw (Exception. ^String (or (->> (.getMessage e) ; error message comes back like 'Column "ZID" not found; SQL statement: ... [error-code]' sometimes + (re-find #"^(.*);") ; the user already knows the SQL, and error code is meaningless + second) ; so just return the part of the exception that is relevant + (.getMessage e))))))) diff --git a/src/metabase/driver/generic_sql/query_processor.clj b/src/metabase/driver/generic_sql/query_processor.clj index 81c80c9e364dca2a93ded3289efe659e6ff24645..63566b1ecc16686bf48129acbdec6bde858e4523 100644 --- a/src/metabase/driver/generic_sql/query_processor.clj +++ b/src/metabase/driver/generic_sql/query_processor.clj @@ -45,11 +45,10 @@ eval (annotate/annotate query)) (catch java.sql.SQLException e - {:status :failed - :error (or (->> (.getMessage e) ; error message comes back like "Error message ... [status-code]" sometimes - (re-find #"(?s)(^.*)\s+\[[\d-]+\]$") ; status code isn't useful and makes unit tests hard to write so strip it off - second) - (.getMessage e))}))) ; (?s) = Pattern.DOTALL - tell regex `.` to match newline characters as well + (throw (Exception. ^String (or (->> (.getMessage e) ; error message comes back like "Error message ... [status-code]" sometimes + (re-find #"(?s)(^.*)\s+\[[\d-]+\]$") ; status code isn't useful and makes unit tests hard to write so strip it off + second) ; (?s) = Pattern.DOTALL - tell regex `.` to match newline characters as well + (.getMessage e))))))) (defn process-and-run diff --git a/src/metabase/driver/query_processor.clj b/src/metabase/driver/query_processor.clj index 47c37615dd14f5eca4247bbb4ca5eee8be807ccd..71d9de238644dff63e83373681cef0b5cd20ed87 100644 --- a/src/metabase/driver/query_processor.clj +++ b/src/metabase/driver/query_processor.clj @@ -188,12 +188,12 @@ (defn post-process "Apply post-processing steps to the RESULTS of a QUERY, such as applying cumulative sum." [driver query results] - (case (keyword (:type query)) - :native results - :query (let [query (:query query)] - (->> results - (post-process-cumulative-sum query) - add-row-count-and-status)))) + (->> (case (keyword (:type query)) + :native results + :query (let [query (:query query)] + (->> results + (post-process-cumulative-sum query)))) + add-row-count-and-status)) ;; # COMMON ANNOTATION FNS diff --git a/test/metabase/api/meta/db_test.clj b/test/metabase/api/meta/db_test.clj index 212ecd1bb8ff1964aa3260cbb55f9e9b8f2d7df3..aa6ac209bc076842e58b892c5cc7ef186c5af4fa 100644 --- a/test/metabase/api/meta/db_test.clj +++ b/test/metabase/api/meta/db_test.clj @@ -5,6 +5,7 @@ [metabase.driver.mongo.test-data :as mongo-test-data] (metabase.models [database :refer [Database]] [table :refer [Table]]) + [metabase.test.data.datasets :as datasets] [metabase.test-data :refer :all] [metabase.test.util :refer [match-$ random-name expect-eval-actual-first]])) @@ -118,36 +119,39 @@ :logo_url nil :report_timezone nil :inherits true}] - [(match-$ (sel :one Database :name db-name) - {:created_at $ - :engine "postgres" - :id $ - :details {:conn_str "host=localhost port=5432 dbname=fakedb user=cam"} - :updated_at $ - :organization org - :name $ - :organization_id @org-id - :description nil}) - (match-$ @mongo-test-data/mongo-test-db - {:created_at $ - :engine "mongo" - :id $ - :details $ - :updated_at $ - :organization org - :name "Mongo Test" - :organization_id @org-id - :description nil}) - (match-$ @test-db - {:created_at $ - :engine "h2" - :id $ - :details $ - :updated_at $ - :organization org - :name "Test Database" - :organization_id @org-id - :description nil})]) + (filter identity + [(datasets/when-testing-dataset :generic-sql + (match-$ (sel :one Database :name db-name) + {:created_at $ + :engine "postgres" + :id $ + :details {:conn_str "host=localhost port=5432 dbname=fakedb user=cam"} + :updated_at $ + :organization org + :name $ + :organization_id @org-id + :description nil})) + (datasets/when-testing-dataset :mongo + (match-$ @mongo-test-data/mongo-test-db + {:created_at $ + :engine "mongo" + :id $ + :details $ + :updated_at $ + :organization org + :name "Mongo Test" + :organization_id @org-id + :description nil})) + (match-$ @test-db + {:created_at $ + :engine "h2" + :id $ + :details $ + :updated_at $ + :organization org + :name "Test Database" + :organization_id @org-id + :description nil})])) (do ;; Delete all the randomly created Databases we've made so far (cascade-delete Database :organization_id @org-id :id [not-in (set [@db-id diff --git a/test/metabase/api/meta/table_test.clj b/test/metabase/api/meta/table_test.clj index abce251d3a973be9d0e2943c242270f2e068654a..9367404d75cbebdb39e16ffdf199b36776e207f3 100644 --- a/test/metabase/api/meta/table_test.clj +++ b/test/metabase/api/meta/table_test.clj @@ -8,6 +8,7 @@ (metabase.models [field :refer [Field]] [foreign-key :refer [ForeignKey]] [table :refer [Table]]) + [metabase.test.data.datasets :as datasets, :refer [*dataset* with-dataset-when-testing]] [metabase.test-data :refer :all] [metabase.test.util :refer [match-$ expect-eval-actual-first]])) @@ -22,14 +23,14 @@ ;; ## GET /api/meta/table?org ;; These should come back in alphabetical order and include relevant metadata -(expect #{{:name "CATEGORIES", :rows 75, :active true, :id (table->id :categories), :db_id @db-id} - {:name "CHECKINS", :rows 1000, :active true, :id (table->id :checkins), :db_id @db-id} - {:name "USERS", :rows 15, :active true, :id (table->id :users), :db_id @db-id} - {:name "VENUES", :rows 100, :active true, :id (table->id :venues), :db_id @db-id} - {:name "categories", :rows 75, :active true, :id (mongo-data/table-name->id :categories), :db_id @mongo-test-db-id} - {:name "checkins", :rows 1000, :active true, :id (mongo-data/table-name->id :checkins), :db_id @mongo-test-db-id} - {:name "users", :rows 15, :active true, :id (mongo-data/table-name->id :users), :db_id @mongo-test-db-id} - {:name "venues", :rows 100, :active true, :id (mongo-data/table-name->id :venues), :db_id @mongo-test-db-id}} +(expect (set (mapcat (fn [dataset-name] + (with-dataset-when-testing dataset-name + (let [db-id (:id (datasets/db *dataset*))] + [{:name (datasets/format-name *dataset* "categories"), :db_id db-id, :active true, :rows 75, :id (datasets/table-name->id *dataset* :categories)} + {:name (datasets/format-name *dataset* "checkins"), :db_id db-id, :active true, :rows 1000, :id (datasets/table-name->id *dataset* :checkins)} + {:name (datasets/format-name *dataset* "users"), :db_id db-id, :active true, :rows 15, :id (datasets/table-name->id *dataset* :users)} + {:name (datasets/format-name *dataset* "venues"), :db_id db-id, :active true, :rows 100, :id (datasets/table-name->id *dataset* :venues)}]))) + @datasets/test-dataset-names)) (->> ((user->client :rasta) :get 200 "meta/table" :org @org-id) (map #(dissoc % :db :created_at :updated_at :entity_name :description :entity_type)) set)) diff --git a/test/metabase/driver/generic_sql/native_test.clj b/test/metabase/driver/generic_sql/native_test.clj index 87295f434676a826f609cd86c153581b1437f53d..941a9bb00f0a78de87c8172e130924aff60a4c23 100644 --- a/test/metabase/driver/generic_sql/native_test.clj +++ b/test/metabase/driver/generic_sql/native_test.clj @@ -1,6 +1,6 @@ (ns metabase.driver.generic-sql.native-test (:require [expectations :refer :all] - [metabase.driver.generic-sql.native :refer :all] + [metabase.driver :as driver] [metabase.test-data :refer :all])) ;; Just check that a basic query works @@ -10,8 +10,8 @@ [99]] :columns [:id] :cols [{:name :id, :base_type :IntegerField}]}} - (process-and-run {:native {:query "SELECT ID FROM VENUES ORDER BY ID DESC LIMIT 2;"} - :database @db-id})) + (driver/process-query {:native {:query "SELECT ID FROM VENUES ORDER BY ID DESC LIMIT 2;"} + :database @db-id})) ;; Check that column ordering is maintained (expect @@ -23,11 +23,11 @@ :cols [{:name :id, :base_type :IntegerField} {:name :name, :base_type :TextField} {:name :category_id, :base_type :IntegerField}]}} - (process-and-run {:native {:query "SELECT ID, NAME, CATEGORY_ID FROM VENUES ORDER BY ID DESC LIMIT 2;"} - :database @db-id})) + (driver/process-query {:native {:query "SELECT ID, NAME, CATEGORY_ID FROM VENUES ORDER BY ID DESC LIMIT 2;"} + :database @db-id})) ;; Check that we get proper error responses for malformed SQL (expect {:status :failed :error "Column \"ZID\" not found"} - (process-and-run {:native {:query "SELECT ZID FROM CHECKINS LIMIT 2;"} - :database @db-id})) + (driver/process-query {:native {:query "SELECT ZID FROM CHECKINS LIMIT 2;"} + :database @db-id})) diff --git a/test/metabase/driver/mongo/test_data.clj b/test/metabase/driver/mongo/test_data.clj index 9392ccc77daa1d54f5ab44334210417f4ef821eb..2651a081ab9fe2414128dc77820ad082c437d04f 100644 --- a/test/metabase/driver/mongo/test_data.clj +++ b/test/metabase/driver/mongo/test_data.clj @@ -39,22 +39,27 @@ ^{:doc "A delay that fetches or creates the Mongo test `Database`. If DB is created, `load-data` and `sync-database!` are called to get the DB in a state that we can use for testing."} mongo-test-db - (delay (let [db (or (sel :one Database :name mongo-test-db-name) - (let [db (ins Database - :organization_id @org-id - :name mongo-test-db-name - :engine :mongo - :details {:conn_str mongo-test-db-conn-str})] - (log/info (color/cyan "Loading Mongo test data...")) - (load-data) - (driver/sync-database! db) - (set-field-special-types!) - (log/info (color/cyan "Done.")) - db))] - (assert (and (map? db) - (integer? (:id db)) - (exists? Database :id (:id db)))) - db))) + (delay + ;; Resolve metabase.test.data.datasets at runtime to avoid circular dependency + (require 'metabase.test.data.datasets) + (assert (contains? @@(ns-resolve 'metabase.test.data.datasets 'test-dataset-names) :mongo) + "Why are we attempting to use the Mongo test Database when we're not testing against mongo?") + (let [db (or (sel :one Database :name mongo-test-db-name) + (let [db (ins Database + :organization_id @org-id + :name mongo-test-db-name + :engine :mongo + :details {:conn_str mongo-test-db-conn-str})] + (log/info (color/cyan "Loading Mongo test data...")) + (load-data) + (driver/sync-database! db) + (set-field-special-types!) + (log/info (color/cyan "Done.")) + db))] + (assert (and (map? db) + (integer? (:id db)) + (exists? Database :id (:id db)))) + db))) (defonce ^{:doc "A Delay that returns the ID of `mongo-test-db`, forcing creation of it if needed."} diff --git a/test/metabase/driver/mongo_test.clj b/test/metabase/driver/mongo_test.clj index bb94e19e935da54408284a0d9e5964297ad07e3d..2538795bb407aadda7b2b44277511e360c8fcf1d 100644 --- a/test/metabase/driver/mongo_test.clj +++ b/test/metabase/driver/mongo_test.clj @@ -10,8 +10,16 @@ (metabase.models [field :refer [Field]] [table :refer [Table]]) [metabase.test-data.data :refer [test-data]] + [metabase.test.data.datasets :as datasets] [metabase.test.util :refer [expect-eval-actual-first resolve-private-fns]])) +;; ## Logic for selectively running mongo + +(defmacro expect-when-testing-mongo [expected actual] + `(datasets/expect-when-testing-dataset :mongo + ~expected + ~actual)) + ;; ## Constants + Helper Fns/Macros ;; TODO - move these to metabase.test-data ? (def ^:private ^:const table-names @@ -56,35 +64,35 @@ ;; ## Tests for connection functions -(expect true +(expect-when-testing-mongo true (driver/can-connect? @mongo-test-db)) -(expect false +(expect-when-testing-mongo false (driver/can-connect? {:engine :mongo :details {:conn_str "mongodb://123.4.5.6/bad-db-name?connectTimeoutMS=50"}})) ; timeout after 50ms instead of 10s so test's don't take forever -(expect false +(expect-when-testing-mongo false (driver/can-connect? {:engine :mongo :details {:conn_str "mongodb://localhost:3000/bad-db-name?connectTimeoutMS=50"}})) -(expect false +(expect-when-testing-mongo false (driver/can-connect-with-details? :mongo {})) -(expect true +(expect-when-testing-mongo true (driver/can-connect-with-details? :mongo {:host "localhost" :port 27017 :dbname "metabase-test"})) ;; should use default port 27017 if not specified -(expect true +(expect-when-testing-mongo true (driver/can-connect-with-details? :mongo {:host "localhost" :dbname "metabase-test"})) -(expect false +(expect-when-testing-mongo false (driver/can-connect-with-details? :mongo {:host "123.4.5.6" :dbname "bad-db-name?connectTimeoutMS=50"})) -(expect false +(expect-when-testing-mongo false (driver/can-connect-with-details? :mongo {:host "localhost" :port 3000 :dbname "bad-db-name?connectTimeoutMS=50"})) @@ -95,12 +103,12 @@ (resolve-private-fns metabase.driver.mongo field->base-type table->column-names) ;; ### active-table-names -(expect +(expect-when-testing-mongo #{"checkins" "categories" "users" "venues"} (i/active-table-names mongo/driver @mongo-test-db)) ;; ### table->column-names -(expect +(expect-when-testing-mongo [#{:_id :name} #{:_id :date :venue_id :user_id} #{:_id :name :last_login} @@ -110,7 +118,7 @@ (map table->column-names))) ;; ### field->base-type -(expect +(expect-when-testing-mongo [:IntegerField ; categories._id :TextField ; categories.name :IntegerField ; checkins._id @@ -131,7 +139,7 @@ (mapv field->base-type))) ;; ### active-column-names->type -(expect +(expect-when-testing-mongo [{"_id" :IntegerField, "name" :TextField} {"_id" :IntegerField, "date" :DateField, "venue_id" :IntegerField, "user_id" :IntegerField} {"_id" :IntegerField, "name" :TextField, "last_login" :DateField} @@ -141,7 +149,7 @@ (mapv (partial i/active-column-names->type mongo/driver)))) ;; ### table-pks -(expect +(expect-when-testing-mongo [#{"_id"} #{"_id"} #{"_id"} #{"_id"}] ; _id for every table (->> table-names (map table-name->fake-table) @@ -151,7 +159,7 @@ ;; ## Big-picture tests for the way data should look post-sync ;; Test that Tables got synced correctly, and row counts are correct -(expect +(expect-when-testing-mongo [{:rows 75, :active true, :name "categories"} {:rows 1000, :active true, :name "checkins"} {:rows 15, :active true, :name "users"} @@ -159,7 +167,7 @@ (sel :many :fields [Table :name :active :rows] :db_id @mongo-test-db-id (k/order :name))) ;; Test that Fields got synced correctly, and types are correct -(expect +(expect-when-testing-mongo [[{:special_type :id, :base_type :IntegerField, :name "_id"} {:special_type :category, :base_type :DateField, :name "last_login"} {:special_type :category, :base_type :TextField, :name "name"}] diff --git a/test/metabase/driver/query_processor_test.clj b/test/metabase/driver/query_processor_test.clj index 104b3f2a6ec3561a92bee04ec46d97ae8d0354a0..dc601c4528338820d5664601af037463007821bb 100644 --- a/test/metabase/driver/query_processor_test.clj +++ b/test/metabase/driver/query_processor_test.clj @@ -4,67 +4,37 @@ [metabase.db :refer :all] [metabase.driver :as driver] (metabase.models [table :refer [Table]]) - [metabase.test.data.datasets :as datasets])) - -;; ## Functionality for writing driver-independent tests - -(def ^:dynamic *db* - "Bound to `Database` for the current driver inside body of `with-dataset`.") -(def ^:dynamic *db-id* - "Bound to ID of `Database` for the current driver inside body of `with-dataset`.") -(def ^:dynamic *driver-dataset*) - - -;; ### EXPECT-WITH-ALL-DRIVERS - -(defmacro with-dataset - "Execute BODY with `*driver-dataset*`, `*db*` and `*db-id*` bound to appropriate values for DRIVER-NAME." - [dataset-name & body] - {:pre [(keyword? dataset-name) - (contains? datasets/all-valid-dataset-names dataset-name)]} - `(let [dataset# (datasets/dataset-name->dataset ~dataset-name) - db# (datasets/db dataset#)] - (binding [*driver-dataset* dataset# - *db* db# - *db-id* (:id db#)] - (assert (and (integer? *db-id*) - (map? *db*))) - ~@body))) - -(defmacro expect-with-all-datasets - "Like expect, but runs a test inside of `with-dataset` for *each* of the available datasets." - [expected actual] - `(do ~@(mapcat (fn [dataset-name] - `[(expect - (with-dataset ~dataset-name - ~expected) - (with-dataset ~dataset-name - ~actual))]) - @datasets/test-dataset-names))) - + [metabase.test.data.datasets :as datasets :refer [*dataset* expect-with-all-datasets]])) ;; ## Dataset-Independent Data Fns -(defn ->table - "Given keyword TABLE-NAME, fetch corresponding `Table` in `*db*`." - [table-name] - {:pre [*driver-dataset*] - :post [(map? %)]} - (datasets/table-name->table *driver-dataset* table-name)) - (defn id "Return the ID of a `Table` or `Field` for the current driver data set." ([table-name] - {:pre [*driver-dataset* + {:pre [*dataset* (keyword? table-name)] :post [(integer? %)]} - (:id (->table table-name))) + (datasets/table-name->id *dataset* table-name)) ([table-name field-name] - {:pre [*driver-dataset* + {:pre [*dataset* (keyword? table-name) (keyword? field-name)] :post [(integer? %)]} - (datasets/field-name->id *driver-dataset* table-name field-name))) + (datasets/field-name->id *dataset* table-name field-name))) + +(defn db-id [] + {:pre [*dataset*] + :post [(integer? %)]} + (:id (datasets/db *dataset*))) + +(defn fks-supported? [] + (datasets/fks-supported? *dataset*)) + +(defn format-name [name] + (datasets/format-name *dataset* name)) + +(defn id-field-type [] + (datasets/id-field-type *dataset*)) ;; ## Dataset-Independent QP Tests @@ -79,18 +49,9 @@ :row_count ~(count (:rows data)) :data ~data} (driver/process-query {:type :query - :database *db-id* + :database (db-id) :query ~query}))) -(defn fks-supported? [] - (datasets/fks-supported? *driver-dataset*)) - -(defn format-name [name] - (datasets/format-name *driver-dataset* name)) - -(defn id-field-type [] - (datasets/id-field-type *driver-dataset*)) - (defn ->columns "Generate the vector that should go in the `columns` part of a QP result; done by calling `format-name` against each column name." [& names] @@ -547,7 +508,7 @@ :row_count 0 :data {:rows [], :columns [], :cols []}} (driver/process-query {:type :query - :database *db-id* + :database (db-id) :native {} :query {:source_table 0 :filter [nil nil] diff --git a/test/metabase/test/data/datasets.clj b/test/metabase/test/data/datasets.clj index 84e54a349a2d18769ede05eeecca770addf8e8ae..9a3f89503913c175c0109b1a4ffa8f54475139a5 100644 --- a/test/metabase/test/data/datasets.clj +++ b/test/metabase/test/data/datasets.clj @@ -4,6 +4,7 @@ [clojure.tools.logging :as log] [colorize.core :as color] [environ.core :refer [env]] + [expectations :refer :all] [metabase.driver.mongo.test-data :as mongo-data] [metabase.test-data :as generic-sql-data])) @@ -17,6 +18,8 @@ "Return `Database` containing test data for this driver.") (table-name->table [this table-name] "Given a TABLE-NAME keyword like `:venues`, *fetch* the corresponding `Table`.") + (table-name->id [this table-name] + "Given a TABLE-NAME keyword like `:venues`, return corresponding `Table` ID.") (field-name->id [this table-name field-name] "Given keyword TABLE-NAME and FIELD-NAME, return the corresponding `Field` ID.") (fks-supported? [this] @@ -38,11 +41,12 @@ (mongo-data/destroy!) @mongo-data/mongo-test-db (assert (integer? @mongo-data/mongo-test-db-id))) - (db [_] @mongo-data/mongo-test-db) (table-name->table [_ table-name] (mongo-data/table-name->table table-name)) + (table-name->id [_ table-name] + (mongo-data/table-name->id table-name)) (field-name->id [_ table-name field-name] (mongo-data/field-name->id table-name (if (= field-name :id) :_id field-name))) @@ -51,7 +55,6 @@ (format-name [_ table-or-field-name] (if (= table-or-field-name "id") "_id" table-or-field-name)) - (id-field-type [_] :IntegerField)) @@ -63,19 +66,18 @@ (load-data! [_] @generic-sql-data/test-db (assert (integer? @generic-sql-data/db-id))) - (db [_] @generic-sql-data/test-db) (table-name->table [_ table-name] (generic-sql-data/table-name->table table-name)) + (table-name->id [_ table-name] + (generic-sql-data/table->id table-name)) (field-name->id [_ table-name field-name] (generic-sql-data/field->id table-name field-name)) - (fks-supported? [_] true) (format-name [_ table-or-field-name] (clojure.string/upper-case table-or-field-name)) - (id-field-type [_] :BigIntegerField)) @@ -124,3 +126,61 @@ all-valid-dataset-names)] (log/info (color/green "Running QP tests against these datasets: " datasets)) datasets))) + + +;; # Helper Macros + +(def ^:dynamic *dataset* + "The dataset we're currently testing against, bound by `with-dataset`." + nil) + +(defmacro with-dataset + "Bind `*dataset*` to the dataset with DATASET-NAME and execute BODY." + [dataset-name & body] + `(binding [*dataset* (dataset-name->dataset ~dataset-name)] + ~@body)) + +(defmacro when-testing-dataset + "Execute BODY only if we're currently testing against DATASET-NAME." + [dataset-name & body] + `(when (contains? @test-dataset-names ~dataset-name) + ~@body)) + +(defmacro with-dataset-when-testing + "When testing DATASET-NAME, binding `*dataset*` and executes BODY." + [dataset-name & body] + `(when-testing-dataset ~dataset-name + (with-dataset ~dataset-name + ~@body))) + +(defmacro expect-when-testing-dataset + "Generate a unit test that only runs if we're currently testing against DATASET-NAME." + [dataset-name expected actual] + `(expect + (when-testing-dataset ~dataset-name + ~expected) + (when-testing-dataset ~dataset-name + ~actual))) + +(defmacro expect-with-dataset + "Generate a unit test that only runs if we're currently testing against DATASET-NAME, and that binds `*dataset*` to the current dataset." + [dataset-name expected actual] + `(expect-when-testing-dataset ~dataset-name + (with-dataset ~dataset-name + ~expected) + (with-dataset ~dataset-name + ~actual))) + +(defmacro expect-with-datasets + "Generate unit tests for all datasets in DATASET-NAMES; each test will only run if we're currently testing the corresponding dataset. + `*dataset*` is bound to the current dataset inside each test." + [dataset-names expected actual] + `(do ~@(map (fn [dataset-name] + `(expect-with-dataset ~dataset-name ~expected ~actual)) + dataset-names))) + +(defmacro expect-with-all-datasets + "Generate unit tests for all valid datasets; each test will only run if we're currently testing the corresponding dataset. + `*dataset*` is bound to the current dataset inside each test." + [expected actual] + `(expect-with-datasets ~all-valid-dataset-names ~expected ~actual))