1# Elixir Test Suite 2 3Proof of concept porting the JS test suite to Elixir. 4 5Currently the basics.js suite has been partially ported over. 6 7To run the suite: 8 9``` 10mix deps.get 11mix test --trace 12``` 13 14## Set CouchDB credentials 15 16By default the Elixir tests require CouchDB running at http://127.0.0.1:15984 with credentials `adm:pass`. 17You can override those using the following: 18 19``` 20$ EX_USERNAME=myusername EX_PASSWORD=password EX_COUCH_URL=http://my-couchdb.com mix test 21``` 22 23## Tests to port 24 25X means done, - means partially 26 27 - [X] Port all_docs.js 28 - [X] Port attachment_names.js 29 - [X] Port attachment_paths.js 30 - [X] Port attachment_ranges.js 31 - [X] Port attachments.js 32 - [X] Port attachments_multipart.js 33 - [X] Port attachment_views.js 34 - [X] Port auth_cache.js 35 - [X] Port basics.js 36 - [X] Port batch_save.js 37 - [X] Port bulk_docs.js 38 - [X] Port changes.js 39 - [X] Port coffee.js 40 - [X] Port compact.js 41 - [X] Port config.js 42 - [X] Port conflicts.js 43 - [X] Port cookie_auth.js 44 - [X] Port copy_doc.js 45 - [X] Port design_docs.js 46 - [X] Port design_docs_query.js 47 - [X] Port design_options.js 48 - [X] Port design_paths.js 49 - [X] Port erlang_views.js 50 - [X] Port etags_head.js 51 - [ ] ~~Port etags_views.js~~ (skipped in js test suite) 52 - [X] Port form_submit.js 53 - [X] Port http.js 54 - [X] Port invalid_docids.js 55 - [X] Port jsonp.js 56 - [X] Port large_docs.js 57 - [X] Port list_views.js 58 - [X] Port lorem_b64.txt 59 - [X] Port lorem.txt 60 - [X] Port lots_of_docs.js 61 - [X] Port method_override.js 62 - [X] Port multiple_rows.js 63 - [X] Port proxyauth.js 64 - [X] Port purge.js 65 - [X] Port reader_acl.js 66 - [X] Port recreate_doc.js 67 - [X] Port reduce_builtin.js 68 - [X] Port reduce_false.js 69 - [ ] ~~Port reduce_false_temp.js~~ 70 - [X] Port reduce.js 71 - [X] Port replication.js 72 - [ ] Port replicator_db_bad_rep_id.js 73 - [ ] Port replicator_db_by_doc_id.js 74 - [ ] Port replicator_db_compact_rep_db.js 75 - [ ] Port replicator_db_continuous.js 76 - [ ] Port replicator_db_credential_delegation.js 77 - [ ] Port replicator_db_field_validation.js 78 - [ ] Port replicator_db_filtered.js 79 - [ ] Port replicator_db_identical_continuous.js 80 - [ ] Port replicator_db_identical.js 81 - [ ] Port replicator_db_invalid_filter.js 82 - [ ] Port replicator_db_security.js 83 - [ ] Port replicator_db_simple.js 84 - [ ] Port replicator_db_successive.js 85 - [ ] Port replicator_db_survives.js 86 - [ ] Port replicator_db_swap_rep_db.js 87 - [ ] Port replicator_db_update_security.js 88 - [ ] Port replicator_db_user_ctx.js 89 - [ ] Port replicator_db_write_auth.js 90 - [X] Port rev_stemming.js 91 - [X] Port rewrite.js 92 - [X] Port rewrite_js.js 93 - [X] Port security_validation.js 94 - [X] Port show_documents.js 95 - [ ] Port stats.js 96 - [X] Port update_documents.js 97 - [X] Port users_db.js 98 - [X] Port users_db_security.js 99 - [X] Port utf8.js 100 - [X] Port uuids.js 101 - [X] Port view_collation.js 102 - [X] Port view_collation_raw.js 103 - [X] Port view_compaction.js 104 - [X] Port view_conflicts.js 105 - [X] Port view_errors.js 106 - [X] Port view_include_docs.js 107 - [X] Port view_multi_key_all_docs.js 108 - [X] Port view_multi_key_design.js 109 - [ ] ~~Port view_multi_key_temp.js~~ 110 - [X] Port view_offsets.js 111 - [X] Port view_pagination.js 112 - [X] Port view_sandboxing.js 113 - [X] Port view_update_seq.js 114 115# Using ExUnit to write unit tests 116 117Elixir has a number of benefits which makes writing unit tests easier. 118For example it is trivial to do codegeneration of tests. 119Bellow we present a few use cases where code-generation is really helpful. 120 121## How to write ExUnit tests 122 1231. Create new file in test/exunit/ directory (the file name should match *_test.exs) 1242. In case it is a first file in the directory create test_helper.exs (look at src/couch/test/exunit/test_helper.exs to get an idea) 1253. define test module which does `use Couch.Test.ExUnit.Case` 1264. Define test cases in the module 127 128You can run tests either: 129- using make: `make exunit` 130- using mix: BUILDDIR=`pwd` ERL_LIBS=`pwd`/src MIX_ENV=test mix test --trace 131 132## Generating tests from spec 133 134Sometimes we have some data in structured format and want 135to generate test cases using that data. This is easy in Elixir. 136For example suppose we have following spec: 137``` 138{ 139 "{db_name}/_view_cleanup": { 140 "roles": ["_admin"] 141 } 142} 143``` 144We can use this spec to generate test cases 145``` 146defmodule GenerateTestsFromSpec do 147 use ExUnit.Case 148 require Record 149 Record.defrecordp :user_ctx, Record.extract(:user_ctx, from_lib: "couch/include/couch_db.hrl") 150 Record.defrecordp :httpd, Record.extract(:httpd, from_lib: "couch/include/couch_db.hrl") 151 152 {:ok, spec_bin} = File.read("roles.json") 153 spec = :jiffy.decode(spec_bin, [:return_maps]) 154 Enum.each spec, fn {path, path_spec} -> 155 roles = path_spec["roles"] 156 @roles roles 157 @path_parts String.split(path, "/") 158 test "Access with `#{inspect(roles)}` roles" do 159 req = httpd(path_parts: @path_parts, user_ctx: user_ctx(roles: @roles)) 160 :chttpd_auth_request.authorize_request(req) 161 end 162 end 163end 164``` 165As a result we would get 166``` 167GenerateTestsFromSpec 168 * test Access with `["_admin"]` roles (0.00ms) 169``` 170 171## Test all possible combinations 172 173Sometimes we want to test all possible permutations for parameters. 174This can be accomplished using something like the following: 175 176``` 177defmodule Permutations do 178 use ExUnit.Case 179 pairs = :couch_tests_combinatorics.product([ 180 [:remote, :local], [:remote, :local] 181 ]) 182 for [source, dest] <- pairs do 183 @source source 184 @dest dest 185 test "Replication #{source} -> #{dest}" do 186 assert :ok == :ok 187 end 188 end 189end 190``` 191 192This would produce following tests 193``` 194Permutations 195 * test Replication remote -> remote (0.00ms) 196 * test Replication local -> remote (0.00ms) 197 * test Replication remote -> local (0.00ms) 198 * test Replication local -> local (0.00ms) 199``` 200 201## Reuseing of common setups 202 203The setup functions are quite similar in lots of tests therefore it makes 204sense to reuse them. The idea is to add shared setup functions into either 205- test/elixir/lib/setup/common.ex 206- test/elixir/lib/setup/<something>.ex 207 208The setup functions looks like the following: 209``` 210defmodule Foo do 211 alias Couch.Test.Setup.Step 212 213 def httpd_with_admin(setup) do 214 setup 215 |> Step.Start.new(:start, extra_apps: [:chttpd]) 216 |> Step.User.new(:admin, roles: [:server_admin]) 217 end 218end 219``` 220 221These parts of a setup chain can be invoked as follows: 222``` 223defmodule Couch.Test.CRUD do 224 use Couch.Test.ExUnit.Case 225 alias Couch.Test.Utils 226 227 alias Couch.Test.Setup 228 229 alias Couch.Test.Setup.Step 230 231 def with_db(context, setup) do 232 setup = 233 setup 234 |> Setup.Common.httpd_with_db() 235 |> Setup.run() 236 237 context = 238 Map.merge(context, %{ 239 db_name: setup |> Setup.get(:db) |> Step.Create.DB.name(), 240 base_url: setup |> Setup.get(:start) |> Step.Start.clustered_url(), 241 user: setup |> Setup.get(:admin) |> Step.User.name() 242 }) 243 244 {context, setup} 245 end 246 247 describe "Database CRUD using Fabric API" do 248 @describetag setup: &__MODULE__.with_db/2 249 test "Create DB", ctx do 250 IO.puts("base_url: #{ctx.base_url}") 251 IO.puts("admin: #{ctx.user}") 252 IO.puts("db_name: #{ctx.db_name}") 253 end 254 end 255end 256``` 257