From c7831c5c1b28312c2156da70697ef6bf5f29e3b2 Mon Sep 17 00:00:00 2001 From: Cam Saul <cam@geotip.com> Date: Wed, 29 Apr 2015 20:04:41 -0700 Subject: [PATCH] rework some of the test data stuff --- .dir-locals.el | 1 - src/metabase/db.clj | 14 +-- .../driver/generic_sql/query_processor.clj | 7 +- src/metabase/driver/mongo/query_processor.clj | 9 +- src/metabase/driver/query_processor.clj | 3 + src/metabase/driver/sync.clj | 87 ++++++------- src/metabase/models/database.clj | 4 +- test/metabase/api/meta/table_test.clj | 21 +++- test/metabase/api/query_test.clj | 2 +- test/metabase/api/result_test.clj | 2 +- test/metabase/driver/mongo/test_data.clj | 56 ++++++--- test/metabase/driver/mongo_test.clj | 46 ++++--- test/metabase/driver/query_processor_test.clj | 23 ++-- test/metabase/test/data.clj | 116 ++++++++++++++++++ test/metabase/test/data/h2.clj | 11 ++ test/metabase/test/data/interface.clj | 17 +++ test/metabase/test/data/mongo.clj | 18 +++ test/metabase/test_setup.clj | 15 ++- 18 files changed, 328 insertions(+), 124 deletions(-) create mode 100644 test/metabase/test/data.clj create mode 100644 test/metabase/test/data/h2.clj create mode 100644 test/metabase/test/data/interface.clj create mode 100644 test/metabase/test/data/mongo.clj diff --git a/.dir-locals.el b/.dir-locals.el index ee8ad0ec4f3..ef2abb2e2b4 100644 --- a/.dir-locals.el +++ b/.dir-locals.el @@ -20,7 +20,6 @@ (expect-expansion 0) (expect-let 1) (expect-with-all-drivers 1) - (expect-with-data-loaded 1) (ins 1) (let-400 1) (let-404 1) diff --git a/src/metabase/db.clj b/src/metabase/db.clj index a07581e3314..0d801462892 100644 --- a/src/metabase/db.clj +++ b/src/metabase/db.clj @@ -74,12 +74,6 @@ (config/config-str :mb-db-user) (config/config-str :mb-db-pass)))) -(defn test-db-conn - "Simple test of a JDBC connection." - [jdbc-db] - (let [result (first (jdbc/query jdbc-db ["select 7 as num"] :row-fn :num))] - (assert (= 7 result) "JDBC Connection Test FAILED"))) - ;; ## MIGRATE @@ -98,16 +92,22 @@ (def ^:private setup-db-has-been-called? (atom false)) +(def ^:private db-can-connect? (u/runtime-resolved-fn 'metabase.driver 'can-connect?)) + (defn setup-db "Do general perparation of database by validating that we can connect. Caller can specify if we should run any pending database migrations." [& {:keys [auto-migrate] :or {auto-migrate true}}] (reset! setup-db-has-been-called? true) + (log/info "Setting up DB specs...") (let [jdbc-db (setup-jdbc-db) korma-db (setup-korma-db)] ;; Test DB connection and throw exception if we have any troubles connecting - (test-db-conn jdbc-db) + (log/info "Verifying Database Connection ...") + (assert (db-can-connect? {:engine (config/config-kw :mb-db-type) + :details {:conn_str (metabase-db-conn-str)}}) + "Unable to connect to Metabase DB.") (log/info "Verify Database Connection ... CHECK") ;; Run through our DB migration process and make sure DB is fully prepared (if auto-migrate diff --git a/src/metabase/driver/generic_sql/query_processor.clj b/src/metabase/driver/generic_sql/query_processor.clj index af7f0c73cce..2398f7d09ff 100644 --- a/src/metabase/driver/generic_sql/query_processor.clj +++ b/src/metabase/driver/generic_sql/query_processor.clj @@ -5,9 +5,10 @@ [korma.core :refer :all] [metabase.config :as config] [metabase.db :refer :all] - [metabase.driver.generic-sql.native :as native] + [metabase.driver.query-processor :as qp] + (metabase.driver.generic-sql [native :as native] + [util :refer :all]) [metabase.driver.generic-sql.query-processor.annotate :as annotate] - [metabase.driver.generic-sql.util :refer :all] (metabase.models [database :refer [Database]] [field :refer [Field]] [table :refer [Table]]))) @@ -234,7 +235,7 @@ (defn- log-query "Log QUERY Dictionary and the korma form and SQL that the Query Processor translates it to." [{:keys [source_table] :as query} forms] - (when-not *jdbc-metadata* ; HACK. If *jdbc-metadata* is bound we're probably doing a DB sync. Don't log its hundreds of QP calls, which make it hard to debug. + (when-not qp/*disable-qp-logging* (log/debug "\n********************" "\nSOURCE TABLE: " source_table diff --git a/src/metabase/driver/mongo/query_processor.clj b/src/metabase/driver/mongo/query_processor.clj index 94d4903e4c4..27738f5ca7f 100644 --- a/src/metabase/driver/mongo/query_processor.clj +++ b/src/metabase/driver/mongo/query_processor.clj @@ -1,6 +1,7 @@ (ns metabase.driver.mongo.query-processor (:refer-clojure :exclude [find sort]) (:require [clojure.core.match :refer [match]] + [clojure.tools.logging :as log] [colorize.core :as color] (monger [collection :as mc] [core :as mg] @@ -32,10 +33,10 @@ (with-mongo-connection [_ (sel :one :fields [Database :details] :id database-id)] (case (keyword query-type) :query (let [generated-query (process-structured (:query query))] - ;; ; TODO - log/debug - (println (color/magenta "\n******************** Generated Monger Query: ********************\n" - (with-out-str (clojure.pprint/pprint generated-query)) - "*****************************************************************\n")) + (when-not qp/*disable-qp-logging* + (log/debug (color/magenta "\n******************** Generated Monger Query: ********************\n" + (with-out-str (clojure.pprint/pprint generated-query)) + "*****************************************************************\n"))) (->> (eval generated-query) (annotate-results (:query query)))) :native (->> (eval-raw-command (:query (:native query))) diff --git a/src/metabase/driver/query_processor.clj b/src/metabase/driver/query_processor.clj index 6ecdb14c17d..c17a1fd415a 100644 --- a/src/metabase/driver/query_processor.clj +++ b/src/metabase/driver/query_processor.clj @@ -11,6 +11,9 @@ (def ^:dynamic *query* "The structured query we're currently processing, before any preprocessing occurs (i.e. the `:query` part of the API call body)" nil) +(def ^:dynamic *disable-qp-logging* "Should we disable logging for the QP? (e.g., during sync we probably want to turn it off to keep logs less cluttered)." + false) + (defn preprocess [{query-type :type :as query}] (case (keyword query-type) :query (preprocess-structured query) diff --git a/src/metabase/driver/sync.clj b/src/metabase/driver/sync.clj index 59e42ace339..d2b7ee6b45f 100644 --- a/src/metabase/driver/sync.clj +++ b/src/metabase/driver/sync.clj @@ -5,7 +5,8 @@ [colorize.core :as color] [korma.core :as k] [metabase.db :refer :all] - [metabase.driver.interface :refer :all] + (metabase.driver [interface :refer :all] + [query-processor :as qp]) [metabase.driver.sync.queries :as queries] (metabase.models [field :refer [Field] :as field] [foreign-key :refer [ForeignKey]] @@ -27,52 +28,54 @@ (defn sync-database! "Sync DATABASE and all its Tables and Fields." [driver database] - (sync-in-context driver database - (fn [] - (log/info (color/blue (format "Syncing database %s..." (:name database)))) - - (let [active-table-names (active-table-names driver database) - table-name->id (sel :many :field->id [Table :name] :db_id (:id database) :active true)] - (assert (set? active-table-names) "active-table-names should return a set.") - (assert (every? string? active-table-names) "active-table-names should return the names of Tables as *strings*.") - - ;; First, let's mark any Tables that are no longer active as such. - ;; These are ones that exist in table-name->id but not in active-table-names. - (log/debug "Marking inactive tables...") - (doseq [[table-name table-id] table-name->id] - (when-not (contains? active-table-names table-name) - (upd Table table-id :active false) - (log/info (format "Marked table %s.%s as inactive." (:name database) table-name)) - - ;; We need to mark driver Table's Fields as inactive so we don't expose them in UI such as FK selector (etc.) This can happen in the background - (future (k/update Field - (k/where {:table_id table-id}) - (k/set-fields {:active false}))))) - - ;; Next, we'll create new Tables (ones that came back in active-table-names but *not* in table-name->id) - (log/debug "Creating new tables...") - (let [existing-table-names (set (keys table-name->id))] - (doseq [active-table-name active-table-names] - (when-not (contains? existing-table-names active-table-name) - (ins Table :db_id (:id database), :active true, :name active-table-name) - (log/info (format "Found new table: %s.%s" (:name database) active-table-name)))))) - - ;; Now sync the active tables - (log/debug "Syncing active tables...") - (->> (sel :many Table :db_id (:id database) :active true) - (map #(assoc % :db (delay database))) ; replace default delays with ones that reuse database (and don't require a DB call) - (sync-database-active-tables! driver)) - - (log/info (color/blue (format "Finished syncing database %s." (:name database))))))) + (binding [qp/*disable-qp-logging* true] + (sync-in-context driver database + (fn [] + (log/info (color/blue (format "Syncing database %s..." (:name database)))) + + (let [active-table-names (active-table-names driver database) + table-name->id (sel :many :field->id [Table :name] :db_id (:id database) :active true)] + (assert (set? active-table-names) "active-table-names should return a set.") + (assert (every? string? active-table-names) "active-table-names should return the names of Tables as *strings*.") + + ;; First, let's mark any Tables that are no longer active as such. + ;; These are ones that exist in table-name->id but not in active-table-names. + (log/debug "Marking inactive tables...") + (doseq [[table-name table-id] table-name->id] + (when-not (contains? active-table-names table-name) + (upd Table table-id :active false) + (log/info (format "Marked table %s.%s as inactive." (:name database) table-name)) + + ;; We need to mark driver Table's Fields as inactive so we don't expose them in UI such as FK selector (etc.) This can happen in the background + (future (k/update Field + (k/where {:table_id table-id}) + (k/set-fields {:active false}))))) + + ;; Next, we'll create new Tables (ones that came back in active-table-names but *not* in table-name->id) + (log/debug "Creating new tables...") + (let [existing-table-names (set (keys table-name->id))] + (doseq [active-table-name active-table-names] + (when-not (contains? existing-table-names active-table-name) + (ins Table :db_id (:id database), :active true, :name active-table-name) + (log/info (format "Found new table: %s.%s" (:name database) active-table-name)))))) + + ;; Now sync the active tables + (log/debug "Syncing active tables...") + (->> (sel :many Table :db_id (:id database) :active true) + (map #(assoc % :db (delay database))) ; replace default delays with ones that reuse database (and don't require a DB call) + (sync-database-active-tables! driver)) + + (log/info (color/blue (format "Finished syncing database %s." (:name database)))))))) (defn sync-table! "Sync a *single* TABLE by running all the sync steps for it. This is used *instead* of `sync-database!` when syncing just one Table is desirable." [driver table] (let [database @(:db table)] - (sync-in-context driver database - (fn [] - (sync-database-active-tables! driver [table]))))) + (binding [qp/*disable-qp-logging* true] + (sync-in-context driver database + (fn [] + (sync-database-active-tables! driver [table])))))) ;; ### sync-database-active-tables! -- runs the sync-table steps over sequence of Tables @@ -264,7 +267,6 @@ (extend-protocol ISyncDriverFieldPercentUrls ; Default implementation Object (field-percent-urls [this field] - (log/warn (color/red (format "Using default (read: slow) implementation of field-percent-urls for driver %s." (.getName (class this))))) (assert (extends? ISyncDriverFieldValues (class this)) "A sync driver implementation that doesn't implement ISyncDriverFieldPercentURLs must implement ISyncDriverFieldValues.") (let [field-values (field-values-lazy-seq this field)] @@ -312,7 +314,6 @@ (extend-protocol ISyncDriverFieldAvgLength ; Default implementation Object (field-avg-length [this field] - (log/warn (color/red (format "Using default (read: slow) implementation of field-avg-length for driver %s." (.getName (class this))))) (assert (extends? ISyncDriverFieldValues (class this)) "A sync driver implementation that doesn't implement ISyncDriverFieldAvgLength must implement ISyncDriverFieldValues.") (let [field-values (field-values-lazy-seq this field) diff --git a/src/metabase/models/database.clj b/src/metabase/models/database.clj index e599e9174a0..195fb307592 100644 --- a/src/metabase/models/database.clj +++ b/src/metabase/models/database.clj @@ -18,7 +18,9 @@ :can_read (delay (org-can-read organization_id)) :can_write (delay (org-can-write organization_id)))) -(defmethod pre-cascade-delete Database [_ {:keys [id]}] +(defmethod pre-cascade-delete Database [_ {:keys [id] :as database}] + (if (= (:engine database) :mongo) + (throw (Exception. "WHY ARE WE DESTROYING MONGO DB!"))) (cascade-delete 'metabase.models.table/Table :db_id id) (cascade-delete 'metabase.models.query/Query :database_id id)) diff --git a/test/metabase/api/meta/table_test.clj b/test/metabase/api/meta/table_test.clj index 5e94d113cac..2edb73465fd 100644 --- a/test/metabase/api/meta/table_test.clj +++ b/test/metabase/api/meta/table_test.clj @@ -2,6 +2,7 @@ "Tests for /api/meta/table endpoints." (:require [expectations :refer :all] [metabase.db :refer :all] + [metabase.driver.mongo.test-data :as mongo-data :refer [mongo-test-db-id]] [metabase.http-client :as http] [metabase.middleware.auth :as auth] (metabase.models [field :refer [Field]] @@ -21,10 +22,22 @@ ;; ## GET /api/meta/table?org ;; These should come back in alphabetical order and include relevant metadata -(expect [{:description nil, :entity_type nil, :name "CATEGORIES", :rows 75, :entity_name nil, :active true, :id (table->id :categories), :db_id @db-id} - {:description nil, :entity_type nil, :name "CHECKINS", :rows 1000, :entity_name nil, :active true, :id (table->id :checkins), :db_id @db-id} - {:description nil, :entity_type nil, :name "USERS", :rows 15, :entity_name nil, :active true, :id (table->id :users), :db_id @db-id} - {:description nil, :entity_type nil, :name "VENUES", :rows 100, :entity_name nil, :active true, :id (table->id :venues), :db_id @db-id}] +(expect [{:description nil, :entity_type nil, :name "CATEGORIES", :rows 75, :entity_name nil, :active true, + :id (table->id :categories), :db_id @db-id} + {:description nil, :entity_type nil, :name "CHECKINS", :rows 1000, :entity_name nil, :active true, + :id (table->id :checkins), :db_id @db-id} + {:description nil, :entity_type nil, :name "USERS", :rows 15, :entity_name nil, :active true, + :id (table->id :users), :db_id @db-id} + {:description nil, :entity_type nil, :name "VENUES", :rows 100, :entity_name nil, :active true, + :id (table->id :venues), :db_id @db-id} + {:description nil, :entity_type nil, :name "categories", :rows 75, :entity_name nil, :active true, + :id (mongo-data/table-name->id :categories), :db_id @mongo-test-db-id} + {:description nil, :entity_type nil, :name "checkins", :rows 1000, :entity_name nil, :active true, + :id (mongo-data/table-name->id :checkins), :db_id @mongo-test-db-id} + {:description nil, :entity_type nil, :name "users", :rows 15, :entity_name nil, :active true, + :id (mongo-data/table-name->id :users), :db_id @mongo-test-db-id} + {:description nil, :entity_type nil, :name "venues", :rows 100, :entity_name nil, :active true, + :id (mongo-data/table-name->id :venues), :db_id @mongo-test-db-id}] (->> ((user->client :rasta) :get 200 "meta/table" :org @org-id) (map #(dissoc % :db :created_at :updated_at)))) ; don't care about checking nested DB, and not sure how to compare `:created_at` / `:updated_at` diff --git a/test/metabase/api/query_test.clj b/test/metabase/api/query_test.clj index 12f01577040..46fb5026897 100644 --- a/test/metabase/api/query_test.clj +++ b/test/metabase/api/query_test.clj @@ -3,6 +3,7 @@ (:require [expectations :refer :all] [korma.core :refer :all] [metabase.db :refer :all] + [metabase.driver.mongo.test-data :as mongo-test-data] (metabase.models [database :refer [Database]] [query :refer [Query]] [query-execution :refer [QueryExecution]]) @@ -35,7 +36,6 @@ {:name "Read Only", :id 1} {:name "Read & Write", :id 2}]} (do - @test-db ; force lazy creation of test data / Metabase DB if it doesn't already exist (cascade-delete Database :organization_id @org-id :id [not= (:id @test-db)]) ; delete any other rando test DBs made by other tests ((user->client :rasta) :get 200 "query/form_input" :org @org-id))) diff --git a/test/metabase/api/result_test.clj b/test/metabase/api/result_test.clj index 95de894379a..17b079cc87c 100644 --- a/test/metabase/api/result_test.clj +++ b/test/metabase/api/result_test.clj @@ -13,7 +13,7 @@ [] (let [{query-id :id} (create-query) query-execution ((user->client :rasta) :post 200 (format "query/%d" query-id))] - (Thread/sleep 100) ; Give it 100ms to finish + (Thread/sleep 200) ; Give it 200ms to finish query-execution)) ;; ## GET /result/:id diff --git a/test/metabase/driver/mongo/test_data.clj b/test/metabase/driver/mongo/test_data.clj index eda1d5b6af7..00a89630217 100644 --- a/test/metabase/driver/mongo/test_data.clj +++ b/test/metabase/driver/mongo/test_data.clj @@ -29,24 +29,37 @@ ;; ## MONGO-TEST-DB + OTHER DELAYS -(def mongo-test-db - "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." - (delay (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/debug (color/cyan "Loading Mongo test data...")) - (load-data) - (driver/sync-database! db) - (log/debug (color/cyan "Done.")) - db)))) - -(def mongo-test-db-id - "A Delay that returns the ID of `mongo-test-db`, forcing creation of it if needed." - (delay (:id @mongo-test-db))) +(defn destroy! + "Remove `Database`, `Tables`, and `Fields` for the Mongo test DB." + [] + #_(cascade-delete Database :name mongo-test-db-name)) + +(defonce + ^{: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/debug (color/cyan "Loading Mongo test data...")) + (load-data) + (driver/sync-database! db) + (log/debug (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."} + mongo-test-db-id + (delay (let [id (:id @mongo-test-db)] + (assert (integer? id)) + id))) ;; ## FNS FOR GETTING RELEVANT TABLES / FIELDS @@ -58,7 +71,12 @@ [table-name] {:pre [(keyword? table-name)] :post [(map? %)]} - (sel :one Table :db_id @mongo-test-db-id :name (name table-name))) + (assert (exists? Database :id @mongo-test-db-id) + (format "Database with ID %d no longer exists!?" @mongo-test-db-id)) + (or (sel :one Table :db_id @mongo-test-db-id :name (name table-name)) + (println (colorize.core/red "db_id: " @mongo-test-db-id "\n" + "db: " (with-out-str (clojure.pprint/pprint (sel :one Database :id @mongo-test-db-id))) "\n" + "name: " (name table-name))))) (def ^{:arglists '([table-name])} table-name->id "Return ID of `Table` for Mongo test database (memoized). diff --git a/test/metabase/driver/mongo_test.clj b/test/metabase/driver/mongo_test.clj index 387c6e35828..bb94e19e935 100644 --- a/test/metabase/driver/mongo_test.clj +++ b/test/metabase/driver/mongo_test.clj @@ -39,13 +39,6 @@ [:venues :name] [:venues :price]]) -(defmacro expect-with-data-loaded - "Like `expect`, but forces the test database to be loaded/synced/etc. before running the test." - [expected actual] - `(expect (do @mongo-test-db - ~expected) - ~actual)) - (defn- table-name->fake-table "Return an object that can be passed like a `Table` to driver sync functions." [table-name] @@ -107,7 +100,7 @@ (i/active-table-names mongo/driver @mongo-test-db)) ;; ### table->column-names -(expect-with-data-loaded +(expect [#{:_id :name} #{:_id :date :venue_id :user_id} #{:_id :name :last_login} @@ -117,7 +110,7 @@ (map table->column-names))) ;; ### field->base-type -(expect-with-data-loaded +(expect [:IntegerField ; categories._id :TextField ; categories.name :IntegerField ; checkins._id @@ -158,7 +151,7 @@ ;; ## Big-picture tests for the way data should look post-sync ;; Test that Tables got synced correctly, and row counts are correct -(expect-with-data-loaded +(expect [{:rows 75, :active true, :name "categories"} {:rows 1000, :active true, :name "checkins"} {:rows 15, :active true, :name "users"} @@ -166,19 +159,22 @@ (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-with-data-loaded - [({:special_type :id, :base_type :IntegerField, :field_type :info, :active true, :name "_id"} - {:special_type :category, :base_type :DateField, :field_type :info, :active true, :name "last_login"} - {:special_type :category, :base_type :TextField, :field_type :info, :active true, :name "name"}) - ({:special_type :id, :base_type :IntegerField, :field_type :info, :active true, :name "_id"} - {:special_type :category, :base_type :DateField, :field_type :info, :active true, :name "last_login"} - {:special_type :category, :base_type :TextField, :field_type :info, :active true, :name "name"}) - ({:special_type :id, :base_type :IntegerField, :field_type :info, :active true, :name "_id"} - {:special_type :category, :base_type :DateField, :field_type :info, :active true, :name "last_login"} - {:special_type :category, :base_type :TextField, :field_type :info, :active true, :name "name"}) - ({:special_type :id, :base_type :IntegerField, :field_type :info, :active true, :name "_id"} - {:special_type :category, :base_type :DateField, :field_type :info, :active true, :name "last_login"} - {:special_type :category, :base_type :TextField, :field_type :info, :active true, :name "name"})] +(expect + [[{: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"}] + [{: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"}] + [{: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"}] + [{: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"}]] (let [table->fields (fn [table-name] - (sel :many :fields [Field :name :active :field_type :base_type :special_type] :table_id (table-name->id :users) (k/order :name)))] - (mapv table->fields table-names))) + (sel :many :fields [Field :name :base_type :special_type] + :active true + :table_id (table-name->id :users) + (k/order :name)))] + (map table->fields table-names))) diff --git a/test/metabase/driver/query_processor_test.clj b/test/metabase/driver/query_processor_test.clj index 1f1e4bd40c7..03a150a4f56 100644 --- a/test/metabase/driver/query_processor_test.clj +++ b/test/metabase/driver/query_processor_test.clj @@ -53,7 +53,8 @@ (binding [*driver-data* driver-data# *db* db# *db-id* (:id db#)] - (assert (integer? *db-id*)) + (assert (and (integer? *db-id*) + (map? *db*))) ~@body))) (defmacro expect-with-all-drivers @@ -82,16 +83,16 @@ :post [(integer? %)]} (:id (->table table-name))) -#_(def ^{:arglists '([table-name])} table-name->id - "Given keyword TABLE-NAME, fetch ID of corresponding `Table` in `*db*`." - (let [-table-name->id (memoize (fn [db-id table-name] - {:pre [(integer? db-id) - (keyword? table-name)] - :post [(integer? %)]} - (sel :one :id Table :db_id db-id :name (name table-name))))] - (fn [table-name] - {:pre [(integer? *db-id*)]} - (-table-name->id *db-id* table-name)))) +;; (def ^{:arglists '([table-name])} table-name->id +;; "Given keyword TABLE-NAME, fetch ID of corresponding `Table` in `*db*`." +;; (let [-table-name->id (memoize (fn [db-id table-name] +;; {:pre [(integer? db-id) +;; (keyword? table-name)] +;; :post [(integer? %)]} +;; (sel :one :id Table :db_id db-id :name (name table-name))))] +;; (fn [table-name] +;; {:pre [(integer? *db-id*)]} +;; (-table-name->id *db-id* table-name)))) ;; ## Driver-Independent QP Tests diff --git a/test/metabase/test/data.clj b/test/metabase/test/data.clj new file mode 100644 index 00000000000..3db9abe24a3 --- /dev/null +++ b/test/metabase/test/data.clj @@ -0,0 +1,116 @@ +(ns metabase.test.data + "Eventual replacement for `metabase.test-data`. + Designed to handle multiple test data sets more nicely." + (:require [clojure.tools.logging :as log] + [colorize.core :as color] + [metabase.db :refer :all] + [metabase.driver :as driver] + (metabase.models [database :refer [Database]] + [org :refer [Org]]) + (metabase.test.data [interface :as i])) + (:import metabase.test.data.h2.H2TestData + metabase.test.data.mongo.MongoTestData)) + +;; ## Test Org + +(def ^:const ^:private test-org-name "Test Organization") + +(def test-org + (delay (or (sel :one Org :name test-org-name) + (ins Org + :name test-org-name + :slug "test" + :inherits true)))) + +(def test-org-id + (delay (:id @test-org))) + + +;; ## Test Data Instances + +(def h2 (H2TestData.)) +(def mongo (MongoTestData.)) + +(def engine-name->test-data + {:h2 h2 + :mongo mongo}) + +(def ^:dynamic *engine* + :h2) + +(defn test-data [] + (engine-name->test-data *engine*)) + +(defmacro with-test-data [engine-name & body] + `(binding [*engine* ~engine-name] + (assert (test-data)) + ~@body)) + + +;; ## Implementation-Agnostic Fns + +(defn load! [] + (log/debug (color/cyan (format "Loading %s test data..." (name *engine*)))) + (i/load! (test-data)) + (let [db (ins Database + :engine *engine* + :organization_id @test-org-id + :name (db-name (test-data)) + :details {:conn_str (connection-str (test-data))})] + (driver/sync-database! db) + (log/debug (color/green "Done.")) + db)) + +(defn db [] + (let [db (or (sel :one Database :name (i/db-name (test-data))) + (load!))] + (assert (map? db)) + db)) + +(def db-id + (let [engine-name->db-id (memoize + (fn [engine-name] + {:post [(integer? engine-name)]} + (with-test-data engine-name + (:id (db)))))]) + (fn [] + (engine-name->db-id *engine*))) + +(defn destroy! [] + (cascade-delete Database :name (db-name (test-data)))) + +(defn table-name->table [table-name] + {:pre [(keyword? table-name)] + :post [(map? %)]} + (i/table-name->table (test-data) (db-id) table-name)) + +(def table-name->id + (let [engine+table-name->id (memoize + (fn [engine-name table-name] + {:pre [(keyword? table-name)] + :post [(integer? %)]} + (with-test-data engine-name + (i/table-name->id (test-data) (db-id) table-name))))] + (fn [table-name] + (engine+table-name->id *engine* table-name)))) + +(defn field-name->field [table-name field-name] + {:pre [(keyword? table-name) + (keyword? field-name)] + :post [(map? %)]} + (i/field-name->field (test-data) table-name field-name)) + +(defn field-name->id [table-name field-name] + {:pre [(keyword? table-name) + (keyword? field-name)] + :post [(integer? %)]} + (i/field-name->id (test-data) table-name field-name)) + + +;; ## Fns that Run Across *All* Test Datasets + +(defn load-all! [] + (doseq [[engine test-data] engine-name->test-data] + + (i/load! test-data) + (log/debug (color/green "Ok.")))) diff --git a/test/metabase/test/data/h2.clj b/test/metabase/test/data/h2.clj new file mode 100644 index 00000000000..3936f86ffef --- /dev/null +++ b/test/metabase/test/data/h2.clj @@ -0,0 +1,11 @@ +(ns metabase.test.data.h2 + (:require [metabase.test.data.interface :refer :all])) + +(deftype H2TestData [] + TestData + (load! [_]) + (db-name [_] + "Test Database") + (connection-str [_] + (let [filename (format "%s/target/test-data" (System/getProperty "user.dir"))] + (format "file:%s;AUTO_SERVER=TRUE;DB_CLOSE_DELAY=-1" filename)))) diff --git a/test/metabase/test/data/interface.clj b/test/metabase/test/data/interface.clj new file mode 100644 index 00000000000..65563fff3cd --- /dev/null +++ b/test/metabase/test/data/interface.clj @@ -0,0 +1,17 @@ +(ns metabase.test.data.interface + "`TestData` protocol.") + +(defprotocol TestData + ;; Loading + (load! [this] + "Load test data, if needed. This should create relevant data, and call `metabase.driver.sync-database!`.") + + ;; DB + (db-name [this] + "Name to use for test `Database`.") + (connection-str [this] + "Connection string to use to connect to test `Database`.") + + ;; Fetching Fns + (table-name->table [this db-id table-name]) + (field-name->field [this table-name field-name])) diff --git a/test/metabase/test/data/mongo.clj b/test/metabase/test/data/mongo.clj new file mode 100644 index 00000000000..474ffc3663c --- /dev/null +++ b/test/metabase/test/data/mongo.clj @@ -0,0 +1,18 @@ +(ns metabase.test.data.mongo + (:require [metabase.db :refer :all] + (metabase.models [table :refer [Table]]) + [metabase.test.data.interface :refer :all])) + +(deftype MongoTestData [] + TestData + (load! [_]) + + (db-name [_] + "Mongo Test") + (connection-str [_] + "mongodb://localhost/metabase-test") + + (table-name->table [_ db-id table-name] + (sel :one Table :db_id db-id :name table-name)) + + (field-name->field [_ table-name field-name])) diff --git a/test/metabase/test_setup.clj b/test/metabase/test_setup.clj index 9c8526276f9..5edc65c8e2c 100644 --- a/test/metabase/test_setup.clj +++ b/test/metabase/test_setup.clj @@ -6,7 +6,8 @@ (metabase [core :as core] [db :as db] [task :as task] - [test-data :refer :all]))) + [test-data :as h2-test-data]) + [metabase.driver.mongo.test-data :as mongo-test-data])) (declare clear-test-db) @@ -24,11 +25,17 @@ [] (log/info "Starting up Metabase unit test runner") ;; clear out any previous test data that's lying around + (log/info "Clearing out test DB...") (clear-test-db) - ;; setup the db and migrate up to current schema + (log/info "Setting up test DB and running migrations...") (db/setup-db :auto-migrate true) - ;; this causes the test data to be loaded - @test-db + (log/info "Loading test data: h2...") + @h2-test-data/test-db + (assert (integer? @h2-test-data/db-id)) + (log/info "Loading test data: mongo...") + (mongo-test-data/destroy!) + @mongo-test-data/mongo-test-db + (assert (integer? @mongo-test-data/mongo-test-db-id)) ;; startup test web server (core/start-jetty) ;; start the task runner -- GitLab