diff --git a/exercises/acronym/.meta/tests.toml b/exercises/acronym/.meta/tests.toml
new file mode 100644
index 000000000..33af04891
--- /dev/null
+++ b/exercises/acronym/.meta/tests.toml
@@ -0,0 +1,28 @@
+[canonical-tests]
+
+# basic
+"1e22cceb-c5e4-4562-9afe-aef07ad1eaf4" = true
+
+# lowercase words
+"79ae3889-a5c0-4b01-baf0-232d31180c08" = true
+
+# punctuation
+"ec7000a7-3931-4a17-890e-33ca2073a548" = true
+
+# all caps word
+"32dd261c-0c92-469a-9c5c-b192e94a63b0" = true
+
+# punctuation without whitespace
+"ae2ac9fa-a606-4d05-8244-3bcc4659c1d4" = true
+
+# very long abbreviation
+"0e4b1e7c-1a6d-48fb-81a7-bf65eb9e69f9" = true
+
+# consecutive delimiters
+"6a078f49-c68d-4b7b-89af-33a1a98c28cc" = true
+
+# apostrophes
+"5118b4b1-4572-434c-8d57-5b762e57973e" = true
+
+# underscore emphasis
+"adc12eab-ec2d-414f-b48c-66a4fc06cdef" = true
diff --git a/exercises/all-your-base/.meta/tests.toml b/exercises/all-your-base/.meta/tests.toml
new file mode 100644
index 000000000..363f95a2e
--- /dev/null
+++ b/exercises/all-your-base/.meta/tests.toml
@@ -0,0 +1,64 @@
+[canonical-tests]
+
+# single bit one to decimal
+"5ce422f9-7a4b-4f44-ad29-49c67cb32d2c" = true
+
+# binary to single decimal
+"0cc3fea8-bb79-46ac-a2ab-5a2c93051033" = true
+
+# single decimal to binary
+"f12db0f9-0d3d-42c2-b3ba-e38cb375a2b8" = true
+
+# binary to multiple decimal
+"2c45cf54-6da3-4748-9733-5a3c765d925b" = true
+
+# decimal to binary
+"65ddb8b4-8899-4fcc-8618-181b2cf0002d" = true
+
+# trinary to hexadecimal
+"8d418419-02a7-4824-8b7a-352d33c6987e" = true
+
+# hexadecimal to trinary
+"d3901c80-8190-41b9-bd86-38d988efa956" = true
+
+# 15-bit integer
+"5d42f85e-21ad-41bd-b9be-a3e8e4258bbf" = true
+
+# empty list
+"d68788f7-66dd-43f8-a543-f15b6d233f83" = true
+
+# single zero
+"5e27e8da-5862-4c5f-b2a9-26c0382b6be7" = true
+
+# multiple zeros
+"2e1c2573-77e4-4b9c-8517-6c56c5bcfdf2" = true
+
+# leading zeros
+"3530cd9f-8d6d-43f5-bc6e-b30b1db9629b" = true
+
+# input base is one
+"a6b476a1-1901-4f2a-92c4-4d91917ae023" = true
+
+# input base is zero
+"e21a693a-7a69-450b-b393-27415c26a016" = true
+
+# input base is negative
+"54a23be5-d99e-41cc-88e0-a650ffe5fcc2" = true
+
+# negative digit
+"9eccf60c-dcc9-407b-95d8-c37b8be56bb6" = true
+
+# invalid positive digit
+"232fa4a5-e761-4939-ba0c-ed046cd0676a" = true
+
+# output base is one
+"14238f95-45da-41dc-95ce-18f860b30ad3" = true
+
+# output base is zero
+"73dac367-da5c-4a37-95fe-c87fad0a4047" = true
+
+# output base is negative
+"13f81f42-ff53-4e24-89d9-37603a48ebd9" = true
+
+# both bases are negative
+"0e6c895d-8a5d-4868-a345-309d094cfe8d" = true
diff --git a/exercises/allergies/.meta/tests.toml b/exercises/allergies/.meta/tests.toml
new file mode 100644
index 000000000..0c3ca725d
--- /dev/null
+++ b/exercises/allergies/.meta/tests.toml
@@ -0,0 +1,153 @@
+[canonical-tests]
+
+# Haskell's testing strategy is to use property tests
+# instead of explicitly listing all the cases in problem-specifications.
+# For this reason, the tests below are marked as true.
+# They're covered by the property tests.
+
+# not allergic to anything
+"17fc7296-2440-4ac4-ad7b-d07c321bc5a0" = true
+
+# allergic only to eggs
+"07ced27b-1da5-4c2e-8ae2-cb2791437546" = true
+
+# allergic to eggs and something else
+"5035b954-b6fa-4b9b-a487-dae69d8c5f96" = true
+
+# allergic to something, but not eggs
+"64a6a83a-5723-4b5b-a896-663307403310" = true
+
+# allergic to everything
+"90c8f484-456b-41c4-82ba-2d08d93231c6" = true
+
+# not allergic to anything
+"d266a59a-fccc-413b-ac53-d57cb1f0db9d" = true
+
+# allergic only to peanuts
+"ea210a98-860d-46b2-a5bf-50d8995b3f2a" = true
+
+# allergic to peanuts and something else
+"eac69ae9-8d14-4291-ac4b-7fd2c73d3a5b" = true
+
+# allergic to something, but not peanuts
+"9152058c-ce39-4b16-9b1d-283ec6d25085" = true
+
+# allergic to everything
+"d2d71fd8-63d5-40f9-a627-fbdaf88caeab" = true
+
+# not allergic to anything
+"b948b0a1-cbf7-4b28-a244-73ff56687c80" = true
+
+# allergic only to shellfish
+"9ce9a6f3-53e9-4923-85e0-73019047c567" = true
+
+# allergic to shellfish and something else
+"b272fca5-57ba-4b00-bd0c-43a737ab2131" = true
+
+# allergic to something, but not shellfish
+"21ef8e17-c227-494e-8e78-470a1c59c3d8" = true
+
+# allergic to everything
+"cc789c19-2b5e-4c67-b146-625dc8cfa34e" = true
+
+# not allergic to anything
+"651bde0a-2a74-46c4-ab55-02a0906ca2f5" = true
+
+# allergic only to strawberries
+"b649a750-9703-4f5f-b7f7-91da2c160ece" = true
+
+# allergic to strawberries and something else
+"50f5f8f3-3bac-47e6-8dba-2d94470a4bc6" = true
+
+# allergic to something, but not strawberries
+"23dd6952-88c9-48d7-a7d5-5d0343deb18d" = true
+
+# allergic to everything
+"74afaae2-13b6-43a2-837a-286cd42e7d7e" = true
+
+# not allergic to anything
+"c49a91ef-6252-415e-907e-a9d26ef61723" = true
+
+# allergic only to tomatoes
+"b69c5131-b7d0-41ad-a32c-e1b2cc632df8" = true
+
+# allergic to tomatoes and something else
+"1ca50eb1-f042-4ccf-9050-341521b929ec" = true
+
+# allergic to something, but not tomatoes
+"e9846baa-456b-4eff-8025-034b9f77bd8e" = true
+
+# allergic to everything
+"b2414f01-f3ad-4965-8391-e65f54dad35f" = true
+
+# not allergic to anything
+"978467ab-bda4-49f7-b004-1d011ead947c" = true
+
+# allergic only to chocolate
+"59cf4e49-06ea-4139-a2c1-d7aad28f8cbc" = true
+
+# allergic to chocolate and something else
+"b0a7c07b-2db7-4f73-a180-565e07040ef1" = true
+
+# allergic to something, but not chocolate
+"f5506893-f1ae-482a-b516-7532ba5ca9d2" = true
+
+# allergic to everything
+"02debb3d-d7e2-4376-a26b-3c974b6595c6" = true
+
+# not allergic to anything
+"17f4a42b-c91e-41b8-8a76-4797886c2d96" = true
+
+# allergic only to pollen
+"7696eba7-1837-4488-882a-14b7b4e3e399" = true
+
+# allergic to pollen and something else
+"9a49aec5-fa1f-405d-889e-4dfc420db2b6" = true
+
+# allergic to something, but not pollen
+"3cb8e79f-d108-4712-b620-aa146b1954a9" = true
+
+# allergic to everything
+"1dc3fe57-7c68-4043-9d51-5457128744b2" = true
+
+# not allergic to anything
+"d3f523d6-3d50-419b-a222-d4dfd62ce314" = true
+
+# allergic only to cats
+"eba541c3-c886-42d3-baef-c048cb7fcd8f" = true
+
+# allergic to cats and something else
+"ba718376-26e0-40b7-bbbe-060287637ea5" = true
+
+# allergic to something, but not cats
+"3c6dbf4a-5277-436f-8b88-15a206f2d6c4" = true
+
+# allergic to everything
+"1faabb05-2b98-4995-9046-d83e4a48a7c1" = true
+
+# no allergies
+"f9c1b8e7-7dc5-4887-aa93-cebdcc29dd8f" = true
+
+# just eggs
+"9e1a4364-09a6-4d94-990f-541a94a4c1e8" = true
+
+# just peanuts
+"8851c973-805e-4283-9e01-d0c0da0e4695" = true
+
+# just strawberries
+"2c8943cb-005e-435f-ae11-3e8fb558ea98" = true
+
+# eggs and peanuts
+"6fa95d26-044c-48a9-8a7b-9ee46ec32c5c" = true
+
+# more than eggs but not peanuts
+"19890e22-f63f-4c5c-a9fb-fb6eacddfe8e" = true
+
+# lots of stuff
+"4b68f470-067c-44e4-889f-c9fe28917d2f" = true
+
+# everything
+"0881b7c5-9efa-4530-91bd-68370d054bc7" = true
+
+# no allergen score parts
+"12ce86de-b347-42a0-ab7c-2e0570f0c65b" = true
diff --git a/exercises/alphametics/.meta/tests.toml b/exercises/alphametics/.meta/tests.toml
new file mode 100644
index 000000000..4e0ea3773
--- /dev/null
+++ b/exercises/alphametics/.meta/tests.toml
@@ -0,0 +1,31 @@
+[canonical-tests]
+
+# puzzle with three letters
+"e0c08b07-9028-4d5f-91e1-d178fead8e1a" = true
+
+# solution must have unique value for each letter
+"a504ee41-cb92-4ec2-9f11-c37e95ab3f25" = true
+
+# leading zero solution is invalid
+"4e3b81d2-be7b-4c5c-9a80-cd72bc6d465a" = true
+
+# puzzle with two digits final carry
+"8a3e3168-d1ee-4df7-94c7-b9c54845ac3a" = true
+
+# puzzle with four letters
+"a9630645-15bd-48b6-a61e-d85c4021cc09" = true
+
+# puzzle with six letters
+"3d905a86-5a52-4e4e-bf80-8951535791bd" = true
+
+# puzzle with seven letters
+"4febca56-e7b7-4789-97b9-530d09ba95f0" = true
+
+# puzzle with eight letters
+"12125a75-7284-4f9a-a5fa-191471e0d44f" = true
+
+# puzzle with ten letters
+"fb05955f-38dc-477a-a0b6-5ef78969fffa" = true
+
+# puzzle with ten letters and 199 addends
+"9a101e81-9216-472b-b458-b513a7adacf7" = true
diff --git a/exercises/anagram/.meta/tests.toml b/exercises/anagram/.meta/tests.toml
new file mode 100644
index 000000000..e1f8eac46
--- /dev/null
+++ b/exercises/anagram/.meta/tests.toml
@@ -0,0 +1,45 @@
+[canonical-tests]
+
+# no matches
+"dd40c4d2-3c8b-44e5-992a-f42b393ec373" = true
+
+# detects two anagrams
+"b3cca662-f50a-489e-ae10-ab8290a09bdc" = true
+
+# does not detect anagram subsets
+"a27558ee-9ba0-4552-96b1-ecf665b06556" = true
+
+# detects anagram
+"64cd4584-fc15-4781-b633-3d814c4941a4" = true
+
+# detects three anagrams
+"99c91beb-838f-4ccd-b123-935139917283" = true
+
+# TODO: Add to this track.
+# detects multiple anagrams with different case
+# "78487770-e258-4e1f-a646-8ece10950d90" = true
+
+# does not detect non-anagrams with identical checksum
+"1d0ab8aa-362f-49b7-9902-3d0c668d557b" = true
+
+# detects anagrams case-insensitively
+"9e632c0b-c0b1-4804-8cc1-e295dea6d8a8" = true
+
+# detects anagrams using case-insensitive subject
+"b248e49f-0905-48d2-9c8d-bd02d8c3e392" = true
+
+# detects anagrams using case-insensitive possible matches
+"f367325c-78ec-411c-be76-e79047f4bd54" = true
+
+# does not detect an anagram if the original word is repeated
+"7cc195ad-e3c7-44ee-9fd2-d3c344806a2c" = true
+
+# anagrams must use all letters exactly once
+"9878a1c9-d6ea-4235-ae51-3ea2befd6842" = true
+
+# words are not anagrams of themselves (case-insensitive)
+"85757361-4535-45fd-ac0e-3810d40debc1" = true
+
+# TODO: Add to this track.
+# words other than themselves can be anagrams
+# "a0705568-628c-4b55-9798-82e4acde51ca" = true
diff --git a/exercises/armstrong-numbers/.meta/tests.toml b/exercises/armstrong-numbers/.meta/tests.toml
new file mode 100644
index 000000000..7358d6f14
--- /dev/null
+++ b/exercises/armstrong-numbers/.meta/tests.toml
@@ -0,0 +1,28 @@
+[canonical-tests]
+
+# Zero is an Armstrong number
+"c1ed103c-258d-45b2-be73-d8c6d9580c7b" = true
+
+# Single digit numbers are Armstrong numbers
+"579e8f03-9659-4b85-a1a2-d64350f6b17a" = true
+
+# There are no 2 digit Armstrong numbers
+"2d6db9dc-5bf8-4976-a90b-b2c2b9feba60" = true
+
+# Three digit number that is an Armstrong number
+"509c087f-e327-4113-a7d2-26a4e9d18283" = true
+
+# Three digit number that is not an Armstrong number
+"7154547d-c2ce-468d-b214-4cb953b870cf" = true
+
+# Four digit number that is an Armstrong number
+"6bac5b7b-42e9-4ecb-a8b0-4832229aa103" = true
+
+# Four digit number that is not an Armstrong number
+"eed4b331-af80-45b5-a80b-19c9ea444b2e" = true
+
+# Seven digit number that is an Armstrong number
+"f971ced7-8d68-4758-aea1-d4194900b864" = true
+
+# Seven digit number that is not an Armstrong number
+"7ee45d52-5d35-4fbd-b6f1-5c8cd8a67f18" = true
diff --git a/exercises/atbash-cipher/.meta/tests.toml b/exercises/atbash-cipher/.meta/tests.toml
new file mode 100644
index 000000000..22b1e97a9
--- /dev/null
+++ b/exercises/atbash-cipher/.meta/tests.toml
@@ -0,0 +1,43 @@
+[canonical-tests]
+
+# encode yes
+"2f47ebe1-eab9-4d6b-b3c6-627562a31c77" = true
+
+# encode no
+"b4ffe781-ea81-4b74-b268-cc58ba21c739" = true
+
+# encode OMG
+"10e48927-24ab-4c4d-9d3f-3067724ace00" = true
+
+# encode spaces
+"d59b8bc3-509a-4a9a-834c-6f501b98750b" = true
+
+# encode mindblowingly
+"31d44b11-81b7-4a94-8b43-4af6a2449429" = true
+
+# encode numbers
+"d503361a-1433-48c0-aae0-d41b5baa33ff" = true
+
+# encode deep thought
+"79c8a2d5-0772-42d4-b41b-531d0b5da926" = true
+
+# encode all the letters
+"9ca13d23-d32a-4967-a1fd-6100b8742bab" = true
+
+# decode exercism
+"bb50e087-7fdf-48e7-9223-284fe7e69851" = true
+
+# decode a sentence
+"ac021097-cd5d-4717-8907-b0814b9e292c" = true
+
+# decode numbers
+"18729de3-de74-49b8-b68c-025eaf77f851" = true
+
+# decode all the letters
+"0f30325f-f53b-415d-ad3e-a7a4f63de034" = true
+
+# decode with too many spaces
+"39640287-30c6-4c8c-9bac-9d613d1a5674" = true
+
+# decode with no spaces
+"b34edf13-34c0-49b5-aa21-0768928000d5" = true
diff --git a/exercises/beer-song/.meta/tests.toml b/exercises/beer-song/.meta/tests.toml
new file mode 100644
index 000000000..4fef6a84f
--- /dev/null
+++ b/exercises/beer-song/.meta/tests.toml
@@ -0,0 +1,29 @@
+[canonical-tests]
+
+# All tests other than "all verses" are marked false here,
+# since the Haskell track made a deliberate choice to test the full song only.
+# https://github.com/exercism/haskell/pull/390
+
+# first generic verse
+"5a02fd08-d336-4607-8006-246fe6fa9fb0" = false
+
+# last generic verse
+"77299ca6-545e-4217-a9cc-606b342e0187" = false
+
+# verse with 2 bottles
+"102cbca0-b197-40fd-b548-e99609b06428" = false
+
+# verse with 1 bottle
+"b8ef9fce-960e-4d85-a0c9-980a04ec1972" = false
+
+# verse with 0 bottles
+"c59d4076-f671-4ee3-baaa-d4966801f90d" = false
+
+# first two verses
+"7e17c794-402d-4ca6-8f96-4d8f6ee1ec7e" = false
+
+# last three verses
+"949868e7-67e8-43d3-9bb4-69277fe020fb" = false
+
+# all verses
+"bc220626-126c-4e72-8df4-fddfc0c3e458" = true
diff --git a/exercises/binary-search-tree/.meta/tests.toml b/exercises/binary-search-tree/.meta/tests.toml
new file mode 100644
index 000000000..0f904baaa
--- /dev/null
+++ b/exercises/binary-search-tree/.meta/tests.toml
@@ -0,0 +1,31 @@
+[canonical-tests]
+
+# data is retained
+"e9c93a78-c536-4750-a336-94583d23fafa" = true
+
+# smaller number at left node
+"7a95c9e8-69f6-476a-b0c4-4170cb3f7c91" = true
+
+# same number at left node
+"22b89499-9805-4703-a159-1a6e434c1585" = true
+
+# greater number at right node
+"2e85fdde-77b1-41ed-b6ac-26ce6b663e34" = true
+
+# can create complex tree
+"dd898658-40ab-41d0-965e-7f145bf66e0b" = true
+
+# can sort single number
+"9e0c06ef-aeca-4202-b8e4-97f1ed057d56" = true
+
+# can sort if second number is smaller than first
+"425e6d07-fceb-4681-a4f4-e46920e380bb" = true
+
+# can sort if second number is same as first
+"bd7532cc-6988-4259-bac8-1d50140079ab" = true
+
+# can sort if second number is greater than first
+"b6d1b3a5-9d79-44fd-9013-c83ca92ddd36" = true
+
+# can sort complex tree
+"d00ec9bd-1288-4171-b968-d44d0808c1c8" = true
diff --git a/exercises/binary-search/.meta/tests.toml b/exercises/binary-search/.meta/tests.toml
new file mode 100644
index 000000000..1c35ab1ac
--- /dev/null
+++ b/exercises/binary-search/.meta/tests.toml
@@ -0,0 +1,34 @@
+[canonical-tests]
+
+# finds a value in an array with one element
+"b55c24a9-a98d-4379-a08c-2adcf8ebeee8" = true
+
+# finds a value in the middle of an array
+"73469346-b0a0-4011-89bf-989e443d503d" = true
+
+# finds a value at the beginning of an array
+"327bc482-ab85-424e-a724-fb4658e66ddb" = true
+
+# finds a value at the end of an array
+"f9f94b16-fe5e-472c-85ea-c513804c7d59" = true
+
+# finds a value in an array of odd length
+"f0068905-26e3-4342-856d-ad153cadb338" = true
+
+# finds a value in an array of even length
+"fc316b12-c8b3-4f5e-9e89-532b3389de8c" = true
+
+# identifies that a value is not included in the array
+"da7db20a-354f-49f7-a6a1-650a54998aa6" = true
+
+# a value smaller than the array's smallest value is not found
+"95d869ff-3daf-4c79-b622-6e805c675f97" = true
+
+# a value larger than the array's largest value is not found
+"8b24ef45-6e51-4a94-9eac-c2bf38fdb0ba" = true
+
+# nothing is found in an empty array
+"f439a0fa-cf42-4262-8ad1-64bf41ce566a" = true
+
+# nothing is found when the left and right bounds cross
+"2c353967-b56d-40b8-acff-ce43115eed64" = true
diff --git a/exercises/binary/.meta/tests.toml b/exercises/binary/.meta/tests.toml
new file mode 100644
index 000000000..b9de12366
--- /dev/null
+++ b/exercises/binary/.meta/tests.toml
@@ -0,0 +1,46 @@
+[canonical-tests]
+
+# binary 0 is decimal 0
+"567fc71e-1013-4915-9285-bca0648c0844" = true
+
+# binary 1 is decimal 1
+"c0824fb1-6a0a-4e9a-a262-c6c00af99fa8" = true
+
+# binary 10 is decimal 2
+"4d2834fb-3cc3-4159-a8fd-da1098def8ed" = true
+
+# binary 11 is decimal 3
+"b7b2b649-4a7c-4808-9eb9-caf00529bac6" = true
+
+# binary 100 is decimal 4
+"de761aff-73cd-43c1-9e1f-0417f07b1e4a" = true
+
+# binary 1001 is decimal 9
+"7849a8f7-f4a1-4966-963e-503282d6814c" = true
+
+# binary 11010 is decimal 26
+"836a101c-aecb-473b-ba78-962408dcda98" = true
+
+# binary 10001101000 is decimal 1128
+"1c6822a4-8584-438b-8dd4-40f0f0b66371" = true
+
+# binary ignores leading zeros
+"91ffe632-8374-4016-b1d1-d8400d9f940d" = true
+
+# 2 is not a valid binary digit
+"44f7d8b1-ddc3-4751-8be3-700a538b421c" = true
+
+# a number containing a non-binary digit is invalid
+"c263a24d-6870-420f-b783-628feefd7b6e" = true
+
+# a number with trailing non-binary characters is invalid
+"8d81305b-0502-4a07-bfba-051c5526d7f2" = true
+
+# a number with leading non-binary characters is invalid
+"a7f79b6b-039a-4d42-99b4-fcee56679f03" = true
+
+# a number with internal non-binary characters is invalid
+"9e0ece9d-b8aa-46a0-a22b-3bed2e3f741e" = true
+
+# a number and a word whitespace separated is invalid
+"46c8dd65-0c32-4273-bb0d-f2b111bccfbd" = true
diff --git a/exercises/bob/.meta/tests.toml b/exercises/bob/.meta/tests.toml
new file mode 100644
index 000000000..c803441da
--- /dev/null
+++ b/exercises/bob/.meta/tests.toml
@@ -0,0 +1,80 @@
+[canonical-tests]
+
+# stating something
+"e162fead-606f-437a-a166-d051915cea8e" = true
+
+# shouting
+"73a966dc-8017-47d6-bb32-cf07d1a5fcd9" = true
+
+# shouting gibberish
+"d6c98afd-df35-4806-b55e-2c457c3ab748" = true
+
+# asking a question
+"8a2e771d-d6f1-4e3f-b6c6-b41495556e37" = true
+
+# asking a numeric question
+"81080c62-4e4d-4066-b30a-48d8d76920d9" = true
+
+# asking gibberish
+"2a02716d-685b-4e2e-a804-2adaf281c01e" = true
+
+# TODO: Update Haskell's values to match problem-specifications
+# talking forcefully
+# "c02f9179-ab16-4aa7-a8dc-940145c385f7" = true
+
+# TODO: Update Haskell's values to match problem-specifications
+# using acronyms in regular speech
+# "153c0e25-9bb5-4ec5-966e-598463658bcd" = true
+
+# TODO: Update Haskell's values to match problem-specifications
+# forceful question
+# "a5193c61-4a92-4f68-93e2-f554eb385ec6" = true
+
+# shouting numbers
+"a20e0c54-2224-4dde-8b10-bd2cdd4f61bc" = true
+
+# no letters
+"f7bc4b92-bdff-421e-a238-ae97f230ccac" = true
+
+# question with no letters
+"bb0011c5-cd52-4a5b-8bfb-a87b6283b0e2" = true
+
+# shouting with special characters
+"496143c8-1c31-4c01-8a08-88427af85c66" = true
+
+# TODO: Update Haskell's values to match problem-specifications
+# shouting with no exclamation mark
+# "e6793c1c-43bd-4b8d-bc11-499aea73925f" = true
+
+# statement containing question mark
+"aa8097cc-c548-4951-8856-14a404dd236a" = true
+
+# non-letters with question
+"9bfc677d-ea3a-45f2-be44-35bc8fa3753e" = true
+
+# prattling on
+"8608c508-f7de-4b17-985b-811878b3cf45" = true
+
+# silence
+"bc39f7c6-f543-41be-9a43-fd1c2f753fc0" = true
+
+# prolonged silence
+"d6c47565-372b-4b09-b1dd-c40552b8378b" = true
+
+# alternate silence
+"4428f28d-4100-4d85-a902-e5a78cb0ecd3" = true
+
+# multiple line question
+"66953780-165b-4e7e-8ce3-4bcb80b6385a" = true
+
+# starting with whitespace
+"5371ef75-d9ea-4103-bcfa-2da973ddec1b" = true
+
+# ending with whitespace
+"05b304d6-f83b-46e7-81e0-4cd3ca647900" = true
+
+# other whitespace
+"72bd5ad3-9b2f-4931-a988-dce1f5771de2" = true
+
+# non-question ending with whitespace
+"12983553-8601-46a8-92fa-fcaa3bc4a2a0" = true
diff --git a/exercises/bowling/.meta/tests.toml b/exercises/bowling/.meta/tests.toml
new file mode 100644
index 000000000..db5bbd89a
--- /dev/null
+++ b/exercises/bowling/.meta/tests.toml
@@ -0,0 +1,91 @@
+[canonical-tests]
+
+# should be able to score a game with all zeros
+"656ae006-25c2-438c-a549-f338e7ec7441" = true
+
+# should be able to score a game with no strikes or spares
+"f85dcc56-cd6b-4875-81b3-e50921e3597b" = true
+
+# a spare followed by zeros is worth ten points
+"d1f56305-3ac2-4fe0-8645-0b37e3073e20" = true
+
+# points scored in the roll after a spare are counted twice
+"0b8c8bb7-764a-4287-801a-f9e9012f8be4" = true
+
+# consecutive spares each get a one roll bonus
+"4d54d502-1565-4691-84cd-f29a09c65bea" = true
+
+# a spare in the last frame gets a one roll bonus that is counted once
+"e5c9cf3d-abbe-4b74-ad48-34051b2b08c0" = true
+
+# a strike earns ten points in a frame with a single roll
+"75269642-2b34-4b72-95a4-9be28ab16902" = true
+
+# points scored in the two rolls after a strike are counted twice as a bonus
+"037f978c-5d01-4e49-bdeb-9e20a2e6f9a6" = true
+
+# consecutive strikes each get the two roll bonus
+"1635e82b-14ec-4cd1-bce4-4ea14bd13a49" = true
+
+# a strike in the last frame gets a two roll bonus that is counted once
+"e483e8b6-cb4b-4959-b310-e3982030d766" = true
+
+# rolling a spare with the two roll bonus does not get a bonus roll
+"9d5c87db-84bc-4e01-8e95-53350c8af1f8" = true
+
+# strikes with the two roll bonus do not get bonus rolls
+"576faac1-7cff-4029-ad72-c16bcada79b5" = true
+
+# a strike with the one roll bonus after a spare in the last frame does not get a bonus
+"72e24404-b6c6-46af-b188-875514c0377b" = true
+
+# all strikes is a perfect game
+"62ee4c72-8ee8-4250-b794-234f1fec17b1" = true
+
+# rolls cannot score negative points
+"1245216b-19c6-422c-b34b-6e4012d7459f" = true
+
+# a roll cannot score more than 10 points
+"5fcbd206-782c-4faa-8f3a-be5c538ba841" = true
+
+# two rolls in a frame cannot score more than 10 points
+"fb023c31-d842-422d-ad7e-79ce1db23c21" = true
+
+# bonus roll after a strike in the last frame cannot score more than 10 points
+"6082d689-d677-4214-80d7-99940189381b" = true
+
+# two bonus rolls after a strike in the last frame cannot score more than 10 points
+"e9565fe6-510a-4675-ba6b-733a56767a45" = true
+
+# two bonus rolls after a strike in the last frame can score more than 10 points if one is a strike
+"2f6acf99-448e-4282-8103-0b9c7df99c3d" = true
+
+# the second bonus rolls after a strike in the last frame cannot be a strike if the first one is not a strike
+"6380495a-8bc4-4cdb-a59f-5f0212dbed01" = true
+
+# second bonus roll after a strike in the last frame cannot score more than 10 points
+"2b2976ea-446c-47a3-9817-42777f09fe7e" = true
+
+# an unstarted game cannot be scored
+"29220245-ac8d-463d-bc19-98a94cfada8a" = true
+
+# an incomplete game cannot be scored
+"4473dc5d-1f86-486f-bf79-426a52ddc955" = true
+
+# cannot roll if game already has ten frames
+"2ccb8980-1b37-4988-b7d1-e5701c317df3" = true
+
+# bonus rolls for a strike in the last frame must be rolled before score can be calculated
+"4864f09b-9df3-4b65-9924-c595ed236f1b" = true
+
+# both bonus rolls for a strike in the last frame must be rolled before score can be calculated
+"537f4e37-4b51-4d1c-97e2-986eb37b2ac1" = true
+
+# bonus roll for a spare in the last frame must be rolled before score can be calculated
+"8134e8c1-4201-4197-bf9f-1431afcde4b9" = true
+
+# cannot roll after bonus roll for spare
+"9d4a9a55-134a-4bad-bae8-3babf84bd570" = true
+
+# cannot roll after bonus rolls for strike
+"d3e02652-a799-4ae3-b53b-68582cc604be" = true
diff --git a/exercises/change/.meta/tests.toml b/exercises/change/.meta/tests.toml
new file mode 100644
index 000000000..72b93b582
--- /dev/null
+++ b/exercises/change/.meta/tests.toml
@@ -0,0 +1,34 @@
+[canonical-tests]
+
+# single coin change
+"36887bea-7f92-4a9c-b0cc-c0e886b3ecc8" = true
+
+# multiple coin change
+"cef21ccc-0811-4e6e-af44-f011e7eab6c6" = true
+
+# change with Lilliputian Coins
+"d60952bc-0c1a-4571-bf0c-41be72690cb3" = true
+
+# change with Lower Elbonia Coins
+"408390b9-fafa-4bb9-b608-ffe6036edb6c" = true
+
+# large target values
+"7421a4cb-1c48-4bf9-99c7-7f049689132f" = true
+
+# possible change without unit coins available
+"f79d2e9b-0ae3-4d6a-bb58-dc978b0dba28" = true
+
+# another possible change without unit coins available
+"9a166411-d35d-4f7f-a007-6724ac266178" = true
+
+# no coins make 0 change
+"bbbcc154-e9e9-4209-a4db-dd6d81ec26bb" = true
+
+# error testing for change smaller than the smallest of coins
+"c8b81d5a-49bd-4b61-af73-8ee5383a2ce1" = true
+
+# error if no combination can add up to target
+"3c43e3e4-63f9-46ac-9476-a67516e98f68" = true
+
+# cannot find negative change values
+"8fe1f076-9b2d-4f44-89fe-8a6ccd63c8f3" = true
diff --git a/exercises/clock/.meta/tests.toml b/exercises/clock/.meta/tests.toml
new file mode 100644
index 000000000..a37c96d2d
--- /dev/null
+++ b/exercises/clock/.meta/tests.toml
@@ -0,0 +1,157 @@
+[canonical-tests]
+
+# on the hour
+"a577bacc-106b-496e-9792-b3083ea8705e" = true
+
+# past the hour
+"b5d0c360-3b88-489b-8e84-68a1c7a4fa23" = true
+
+# midnight is zero hours
+"473223f4-65f3-46ff-a9f7-7663c7e59440" = true
+
+# hour rolls over
+"ca95d24a-5924-447d-9a96-b91c8334725c" = true
+
+# hour rolls over continuously
+"f3826de0-0925-4d69-8ac8-89aea7e52b78" = true
+
+# sixty minutes is next hour
+"a02f7edf-dfd4-4b11-b21a-86de3cc6a95c" = true
+
+# minutes roll over
+"8f520df6-b816-444d-b90f-8a477789beb5" = true
+
+# minutes roll over continuously
+"c75c091b-47ac-4655-8d40-643767fc4eed" = true
+
+# hour and minutes roll over
+"06343ecb-cf39-419d-a3f5-dcbae0cc4c57" = true
+
+# hour and minutes roll over continuously
+"be60810e-f5d9-4b58-9351-a9d1e90e660c" = true
+
+# hour and minutes roll over to exactly midnight
+"1689107b-0b5c-4bea-aad3-65ec9859368a" = true
+
+# negative hour
+"d3088ee8-91b7-4446-9e9d-5e2ad6219d91" = true
+
+# negative hour rolls over
+"77ef6921-f120-4d29-bade-80d54aa43b54" = true
+
+# negative hour rolls over continuously
+"359294b5-972f-4546-bb9a-a85559065234" = true
+
+# negative minutes
+"509db8b7-ac19-47cc-bd3a-a9d2f30b03c0" = true
+
+# negative minutes roll over
+"5d6bb225-130f-4084-84fd-9e0df8996f2a" = true
+
+# negative minutes roll over continuously
+"d483ceef-b520-4f0c-b94a-8d2d58cf0484" = true
+
+# negative sixty minutes is previous hour
+"1cd19447-19c6-44bf-9d04-9f8305ccb9ea" = true
+
+# negative hour and minutes both roll over
+"9d3053aa-4f47-4afc-bd45-d67a72cef4dc" = true
+
+# negative hour and minutes both roll over continuously
+"51d41fcf-491e-4ca0-9cae-2aa4f0163ad4" = true
+
+# add minutes
+"d098e723-ad29-4ef9-997a-2693c4c9d89a" = true
+
+# add no minutes
+"b6ec8f38-e53e-4b22-92a7-60dab1f485f4" = true
+
+# add to next hour
+"efd349dd-0785-453e-9ff8-d7452a8e7269" = true
+
+# add more than one hour
+"749890f7-aba9-4702-acce-87becf4ef9fe" = true
+
+# add more than two hours with carry
+"da63e4c1-1584-46e3-8d18-c9dc802c1713" = true
+
+# add across midnight
+"be167a32-3d33-4cec-a8bc-accd47ddbb71" = true
+
+# add more than one day (1500 min = 25 hrs)
+"6672541e-cdae-46e4-8be7-a820cc3be2a8" = true
+
+# add more than two days
+"1918050d-c79b-4cb7-b707-b607e2745c7e" = true
+
+# subtract minutes
+"37336cac-5ede-43a5-9026-d426cbe40354" = true
+
+# subtract to previous hour
+"0aafa4d0-3b5f-4b12-b3af-e3a9e09c047b" = true
+
+# subtract more than an hour
+"9b4e809c-612f-4b15-aae0-1df0acb801b9" = true
+
+# subtract across midnight
+"8b04bb6a-3d33-4e6c-8de9-f5de6d2c70d6" = true
+
+# subtract more than two hours
+"07c3bbf7-ce4d-4658-86e8-4a77b7a5ccd9" = true
+
+# subtract more than two hours with borrow
+"90ac8a1b-761c-4342-9c9c-cdc3ed5db097" = true
+
+# subtract more than one day (1500 min = 25 hrs)
+"2149f985-7136-44ad-9b29-ec023a97a2b7" = true
+
+# subtract more than two days
+"ba11dbf0-ac27-4acb-ada9-3b853ec08c97" = true
+
+# clocks with same time
+"f2fdad51-499f-4c9b-a791-b28c9282e311" = true
+
+# clocks a minute apart
+"5d409d4b-f862-4960-901e-ec430160b768" = true
+
+# clocks an hour apart
+"a6045fcf-2b52-4a47-8bb2-ef10a064cba5" = true
+
+# clocks with hour overflow
+"66b12758-0be5-448b-a13c-6a44bce83527" = true
+
+# clocks with hour overflow by several days
+"2b19960c-212e-4a71-9aac-c581592f8111" = true
+
+# clocks with negative hour
+"6f8c6541-afac-4a92-b0c2-b10d4e50269f" = true
+
+# clocks with negative hour that wraps
+"bb9d5a68-e324-4bf5-a75e-0e9b1f97a90d" = true
+
+# clocks with negative hour that wraps multiple times
+"56c0326d-565b-4d19-a26f-63b3205778b7" = true
+
+# clocks with minute overflow
+"c90b9de8-ddff-4ffe-9858-da44a40fdbc2" = true
+
+# clocks with minute overflow by several days
+"533a3dc5-59a7-491b-b728-a7a34fe325de" = true
+
+# clocks with negative minute
+"fff49e15-f7b7-4692-a204-0f6052d62636" = true
+
+# clocks with negative minute that wraps
+"605c65bb-21bd-43eb-8f04-878edf508366" = true
+
+# clocks with negative minute that wraps multiple times
+"b87e64ed-212a-4335-91fd-56da8421d077" = true
+
+# clocks with negative hours and minutes
+"822fbf26-1f3b-4b13-b9bf-c914816b53dd" = true
+
+# clocks with negative hours and minutes that wrap
+"e787bccd-cf58-4a1d-841c-ff80eaaccfaa" = true
+
+# full clock and zeroed clock
+"96969ca8-875a-48a1-86ae-257a528c44f5" = true
diff --git a/exercises/collatz-conjecture/.meta/tests.toml b/exercises/collatz-conjecture/.meta/tests.toml
new file mode 100644
index 000000000..83f7c89c4
--- /dev/null
+++ b/exercises/collatz-conjecture/.meta/tests.toml
@@ -0,0 +1,19 @@
+[canonical-tests]
+
+# zero steps for one
+"540a3d51-e7a6-47a5-92a3-4ad1838f0bfd" = true
+
+# divide if even
+"3d76a0a6-ea84-444a-821a-f7857c2c1859" = true
+
+# even and odd steps
+"754dea81-123c-429e-b8bc-db20b05a87b9" = true
+
+# large number of even and odd steps
+"ecfd0210-6f85-44f6-8280-f65534892ff6" = true
+
+# zero is an error
+"7d4750e6-def9-4b86-aec7-9f7eb44f95a3" = true
+
+# negative value is an error
+"c6c795bf-a288-45e9-86a1-841359ad426d" = true
diff --git a/exercises/complex-numbers/.meta/tests.toml b/exercises/complex-numbers/.meta/tests.toml
new file mode 100644
index 000000000..5bd57f42e
--- /dev/null
+++ b/exercises/complex-numbers/.meta/tests.toml
@@ -0,0 +1,94 @@
+[canonical-tests]
+
+# Real part of a purely real number
+"9f98e133-eb7f-45b0-9676-cce001cd6f7a" = true
+
+# Real part of a purely imaginary number
+"07988e20-f287-4bb7-90cf-b32c4bffe0f3" = true
+
+# Real part of a number with real and imaginary part
+"4a370e86-939e-43de-a895-a00ca32da60a" = true
+
+# Imaginary part of a purely real number
+"9b3fddef-4c12-4a99-b8f8-e3a42c7ccef6" = true
+
+# Imaginary part of a purely imaginary number
+"a8dafedd-535a-4ed3-8a39-fda103a2b01e" = true
+
+# Imaginary part of a number with real and imaginary part
+"0f998f19-69ee-4c64-80ef-01b086feab80" = true
+
+# Imaginary unit
+"a39b7fd6-6527-492f-8c34-609d2c913879" = true
+
+# Add purely real numbers
+"9a2c8de9-f068-4f6f-b41c-82232cc6c33e" = true
+
+# Add purely imaginary numbers
+"657c55e1-b14b-4ba7-bd5c-19db22b7d659" = true
+
+# Add numbers with real and imaginary part
+"4e1395f5-572b-4ce8-bfa9-9a63056888da" = true
+
+# Subtract purely real numbers
+"1155dc45-e4f7-44b8-af34-a91aa431475d" = true
+
+# Subtract purely imaginary numbers
+"f95e9da8-acd5-4da4-ac7c-c861b02f774b" = true
+
+# Subtract numbers with real and imaginary part
+"f876feb1-f9d1-4d34-b067-b599a8746400" = true
+
+# Multiply purely real numbers
+"8a0366c0-9e16-431f-9fd7-40ac46ff4ec4" = true
+
+# Multiply purely imaginary numbers
+"e560ed2b-0b80-4b4f-90f2-63cefc911aaf" = true
+
+# Multiply numbers with real and imaginary part
+"4d1d10f0-f8d4-48a0-b1d0-f284ada567e6" = true
+
+# Divide purely real numbers
+"b0571ddb-9045-412b-9c15-cd1d816d36c1" = true
+
+# Divide purely imaginary numbers
+"5bb4c7e4-9934-4237-93cc-5780764fdbdd" = true
+
+# Divide numbers with real and imaginary part
+"c4e7fef5-64ac-4537-91c2-c6529707701f" = true
+
+# Absolute value of a positive purely real number
+"c56a7332-aad2-4437-83a0-b3580ecee843" = true
+
+# Absolute value of a negative purely real number
+"cf88d7d3-ee74-4f4e-8a88-a1b0090ecb0c" = true
+
+# Absolute value of a purely imaginary number with positive imaginary part
+"bbe26568-86c1-4bb4-ba7a-da5697e2b994" = true
+
+# Absolute value of a purely imaginary number with negative imaginary part
+"3b48233d-468e-4276-9f59-70f4ca1f26f3" = true
+
+# Absolute value of a number with real and imaginary part
+"fe400a9f-aa22-4b49-af92-51e0f5a2a6d3" = true
+
+# Conjugate a purely real number
+"fb2d0792-e55a-4484-9443-df1eddfc84a2" = true
+
+# Conjugate a purely imaginary number
+"e37fe7ac-a968-4694-a460-66cb605f8691" = true
+
+# Conjugate a number with real and imaginary part
+"f7704498-d0be-4192-aaf5-a1f3a7f43e68" = true
+
+# Euler's identity/formula
+"6d96d4c6-2edb-445b-94a2-7de6d4caaf60" = true
+
+# Exponential of 0
+"2d2c05a0-4038-4427-a24d-72f6624aa45f" = true
+
+# Exponential of a purely real number
+"ed87f1bd-b187-45d6-8ece-7e331232c809" = true
+
+# Exponential of a number with real and imaginary part
+"08eedacc-5a95-44fc-8789-1547b27a8702" = true
diff --git a/exercises/connect/.meta/tests.toml b/exercises/connect/.meta/tests.toml
new file mode 100644
index 000000000..73c717676
--- /dev/null
+++ b/exercises/connect/.meta/tests.toml
@@ -0,0 +1,31 @@
+[canonical-tests]
+
+# an empty board has no winner
+"6eff0df4-3e92-478d-9b54-d3e8b354db56" = true
+
+# X can win on a 1x1 board
+"298b94c0-b46d-45d8-b34b-0fa2ea71f0a4" = true
+
+# O can win on a 1x1 board
+"763bbae0-cb8f-4f28-bc21-5be16a5722dc" = true
+
+# only edges does not make a winner
+"819fde60-9ae2-485e-a024-cbb8ea68751b" = true
+
+# illegal diagonal does not make a winner
+"2c56a0d5-9528-41e5-b92b-499dfe08506c" = true
+
+# nobody wins crossing adjacent angles
+"41cce3ef-43ca-4963-970a-c05d39aa1cc1" = true
+
+# X wins crossing from left to right
+"cd61c143-92f6-4a8d-84d9-cb2b359e226b" = true
+
+# O wins crossing from top to bottom
+"73d1eda6-16ab-4460-9904-b5f5dd401d0b" = true
+
+# X wins using a convoluted path
+"c3a2a550-944a-4637-8b3f-1e1bf1340a3d" = true
+
+# X wins using a spiral path
+"17e76fa8-f731-4db7-92ad-ed2a285d31f3" = true
diff --git a/exercises/crypto-square/.meta/tests.toml b/exercises/crypto-square/.meta/tests.toml
new file mode 100644
index 000000000..84691f128
--- /dev/null
+++ b/exercises/crypto-square/.meta/tests.toml
@@ -0,0 +1,22 @@
+[canonical-tests]
+
+# empty plaintext results in an empty ciphertext
+"407c3837-9aa7-4111-ab63-ec54b58e8e9f" = true
+
+# Lowercase
+"64131d65-6fd9-4f58-bdd8-4a2370fb481d" = true
+
+# Remove spaces
+"63a4b0ed-1e3c-41ea-a999-f6f26ba447d6" = true
+
+# Remove punctuation
+"1b5348a1-7893-44c1-8197-42d48d18756c" = true
+
+# 9 character plaintext results in 3 chunks of 3 characters
+"8574a1d3-4a08-4cec-a7c7-de93a164f41a" = true
+
+# 8 character plaintext results in 3 chunks, the last one with a trailing space
+"a65d3fa1-9e09-43f9-bcec-7a672aec3eae" = true
+
+# 54 character plaintext results in 7 chunks, the last two with trailing spaces
+"fbcb0c6d-4c39-4a31-83f6-c473baa6af80" = true
diff --git a/exercises/custom-set/.meta/tests.toml b/exercises/custom-set/.meta/tests.toml
new file mode 100644
index 000000000..b906845cd
--- /dev/null
+++ b/exercises/custom-set/.meta/tests.toml
@@ -0,0 +1,115 @@
+[canonical-tests]
+
+# sets with no elements are empty
+"20c5f855-f83a-44a7-abdd-fe75c6cf022b" = true
+
+# sets with elements are not empty
+"d506485d-5706-40db-b7d8-5ceb5acf88d2" = true
+
+# nothing is contained in an empty set
+"759b9740-3417-44c3-8ca3-262b3c281043" = true
+
+# when the element is in the set
+"f83cd2d1-2a85-41bc-b6be-80adbff4be49" = true
+
+# when the element is not in the set
+"93423fc0-44d0-4bc0-a2ac-376de8d7af34" = true
+
+# empty set is a subset of another empty set
+"c392923a-637b-4495-b28e-34742cd6157a" = true
+
+# empty set is a subset of non-empty set
+"5635b113-be8c-4c6f-b9a9-23c485193917" = true
+
+# non-empty set is not a subset of empty set
+"832eda58-6d6e-44e2-92c2-be8cf0173cee" = true
+
+# set is a subset of set with exact same elements
+"c830c578-8f97-4036-b082-89feda876131" = true
+
+# set is a subset of larger set with same elements
+"476a4a1c-0fd1-430f-aa65-5b70cbc810c5" = true
+
+# set is not a subset of set that does not contain its elements
+"d2498999-3e46-48e4-9660-1e20c3329d3d" = true
+
+# the empty set is disjoint with itself
+"7d38155e-f472-4a7e-9ad8-5c1f8f95e4cc" = true
+
+# empty set is disjoint with non-empty set
+"7a2b3938-64b6-4b32-901a-fe16891998a6" = true
+
+# non-empty set is disjoint with empty set
+"589574a0-8b48-48ea-88b0-b652c5fe476f" = true
+
+# sets are not disjoint if they share an element
+"febeaf4f-f180-4499-91fa-59165955a523" = true
+
+# sets are disjoint if they share no elements
+"0de20d2f-c952-468a-88c8-5e056740f020" = true
+
+# empty sets are equal
+"4bd24adb-45da-4320-9ff6-38c044e9dff8" = true
+
+# empty set is not equal to non-empty set
+"f65c0a0e-6632-4b2d-b82c-b7c6da2ec224" = true
+
+# non-empty set is not equal to empty set
+"81e53307-7683-4b1e-a30c-7e49155fe3ca" = true
+
+# sets with the same elements are equal
+"d57c5d7c-a7f3-48cc-a162-6b488c0fbbd0" = true
+
+# sets with different elements are not equal
+"dd61bafc-6653-42cc-961a-ab071ee0ee85" = true
+
+# set is not equal to larger set with same elements
+"06059caf-9bf4-425e-aaff-88966cb3ea14" = true
+
+# add to empty set
+"8a677c3c-a658-4d39-bb88-5b5b1a9659f4" = true
+
+# add to non-empty set
+"0903dd45-904d-4cf2-bddd-0905e1a8d125" = true
+
+# adding an existing element does not change the set
+"b0eb7bb7-5e5d-4733-b582-af771476cb99" = true
+
+# intersection of two empty sets is an empty set
+"893d5333-33b8-4151-a3d4-8f273358208a" = true
+
+# intersection of an empty set and non-empty set is an empty set
+"d739940e-def2-41ab-a7bb-aaf60f7d782c" = true
+
+# intersection of a non-empty set and an empty set is an empty set
+"3607d9d8-c895-4d6f-ac16-a14956e0a4b7" = true
+
+# intersection of two sets with no shared elements is an empty set
+"b5120abf-5b5e-41ab-aede-4de2ad85c34e" = true
+
+# intersection of two sets with shared elements is a set of the shared elements
+"af21ca1b-fac9-499c-81c0-92a591653d49" = true
+
+# difference of two empty sets is an empty set
+"c5e6e2e4-50e9-4bc2-b89f-c518f015b57e" = true
+
+# difference of empty set and non-empty set is an empty set
+"2024cc92-5c26-44ed-aafd-e6ca27d6fcd2" = true
+
+# difference of a non-empty set and an empty set is the non-empty set
+"e79edee7-08aa-4c19-9382-f6820974b43e" = true
+
+# difference of two non-empty sets is a set of elements that are only in the first set
+"c5ac673e-d707-4db5-8d69-7082c3a5437e" = true
+
+# union of empty sets is an empty set
+"c45aed16-5494-455a-9033-5d4c93589dc6" = true
+
+# union of an empty set and non-empty set is the non-empty set
+"9d258545-33c2-4fcb-a340-9f8aa69e7a41" = true
+
+# union of a non-empty set and empty set is the non-empty set
+"3aade50c-80c7-4db8-853d-75bac5818b83" = true
+
+# union of non-empty sets contains all unique elements
+"a00bb91f-c4b4-4844-8f77-c73e2e9df77c" = true
diff --git a/exercises/diamond/.meta/tests.toml b/exercises/diamond/.meta/tests.toml
new file mode 100644
index 000000000..3c121906e
--- /dev/null
+++ b/exercises/diamond/.meta/tests.toml
@@ -0,0 +1,16 @@
+[canonical-tests]
+
+# Degenerate case with a single 'A' row
+"202fb4cc-6a38-4883-9193-a29d5cb92076" = true
+
+# Degenerate case with no row containing 3 distinct groups of spaces
+"bd6a6d78-9302-42e9-8f60-ac1461e9abae" = true
+
+# Smallest non-degenerate case with odd diamond side length
+"af8efb49-14ed-447f-8944-4cc59ce3fd76" = true
+
+# Smallest non-degenerate case with even diamond side length
+"e0c19a95-9888-4d05-86a0-fa81b9e70d1d" = true
+
+# Largest possible diamond
+"82ea9aa9-4c0e-442a-b07e-40204e925944" = true
diff --git a/exercises/difference-of-squares/.meta/tests.toml b/exercises/difference-of-squares/.meta/tests.toml
new file mode 100644
index 000000000..3eaac74f5
--- /dev/null
+++ b/exercises/difference-of-squares/.meta/tests.toml
@@ -0,0 +1,28 @@
+[canonical-tests]
+
+# square of sum 1
+"e46c542b-31fc-4506-bcae-6b62b3268537" = true
+
+# square of sum 5
+"9b3f96cb-638d-41ee-99b7-b4f9c0622948" = true
+
+# square of sum 100
+"54ba043f-3c35-4d43-86ff-3a41625d5e86" = true
+
+# sum of squares 1
+"01d84507-b03e-4238-9395-dd61d03074b5" = true
+
+# sum of squares 5
+"c93900cd-8cc2-4ca4-917b-dd3027023499" = true
+
+# sum of squares 100
+"94807386-73e4-4d9e-8dec-69eb135b19e4" = true
+
+# difference of squares 1
+"44f72ae6-31a7-437f-858d-2c0837adabb6" = true
+
+# difference of squares 5
+"005cb2bf-a0c8-46f3-ae25-924029f8b00b" = true
+
+# difference of squares 100
+"b1bf19de-9a16-41c0-a62b-1f02ecc0b036" = true
diff --git a/exercises/dnd-character/.meta/tests.toml b/exercises/dnd-character/.meta/tests.toml
new file mode 100644
index 000000000..5b1242fdb
--- /dev/null
+++ b/exercises/dnd-character/.meta/tests.toml
@@ -0,0 +1,58 @@
+[canonical-tests]
+
+# ability modifier for score 3 is -4
+"1e9ae1dc-35bd-43ba-aa08-e4b94c20fa37" = true
+
+# ability modifier for score 4 is -3
+"cc9bb24e-56b8-4e9e-989d-a0d1a29ebb9c" = true
+
+# ability modifier for score 5 is -3
+"5b519fcd-6946-41ee-91fe-34b4f9808326" = true
+
+# ability modifier for score 6 is -2
+"dc2913bd-6d7a-402e-b1e2-6d568b1cbe21" = true
+
+# ability modifier for score 7 is -2
+"099440f5-0d66-4b1a-8a10-8f3a03cc499f" = true
+
+# ability modifier for score 8 is -1
+"cfda6e5c-3489-42f0-b22b-4acb47084df0" = true
+
+# ability modifier for score 9 is -1
+"c70f0507-fa7e-4228-8463-858bfbba1754" = true
+
+# ability modifier for score 10 is 0
+"6f4e6c88-1cd9-46a0-92b8-db4a99b372f7" = true
+
+# ability modifier for score 11 is 0
+"e00d9e5c-63c8-413f-879d-cd9be9697097" = true
+
+# ability modifier for score 12 is +1
+"eea06f3c-8de0-45e7-9d9d-b8cab4179715" = true
+
+# ability modifier for score 13 is +1
+"9c51f6be-db72-4af7-92ac-b293a02c0dcd" = true
+
+# ability modifier for score 14 is +2
+"94053a5d-53b6-4efc-b669-a8b5098f7762" = true
+
+# ability modifier for score 15 is +2
+"8c33e7ca-3f9f-4820-8ab3-65f2c9e2f0e2" = true
+
+# ability modifier for score 16 is +3
+"c3ec871e-1791-44d0-b3cc-77e5fb4cd33d" = true
+
+# ability modifier for score 17 is +3
+"3d053cee-2888-4616-b9fd-602a3b1efff4" = true
+
+# ability modifier for score 18 is +4
+"bafd997a-e852-4e56-9f65-14b60261faee" = true
+
+# random ability is within range
+"4f28f19c-2e47-4453-a46a-c0d365259c14" = true
+
+# random character is valid
+"385d7e72-864f-4e88-8279-81a7d75b04ad" = true
+
+# each ability is only calculated once
+"2ca77b9b-c099-46c3-a02c-0d0f68ffa0fe" = true
diff --git a/exercises/dominoes/.meta/tests.toml b/exercises/dominoes/.meta/tests.toml
new file mode 100644
index 000000000..40af88d39
--- /dev/null
+++ b/exercises/dominoes/.meta/tests.toml
@@ -0,0 +1,37 @@
+[canonical-tests]
+
+# empty input = empty output
+"31a673f2-5e54-49fe-bd79-1c1dae476c9c" = true
+
+# singleton input = singleton output
+"4f99b933-367b-404b-8c6d-36d5923ee476" = true
+
+# singleton that can't be chained
+"91122d10-5ec7-47cb-b759-033756375869" = true
+
+# three elements
+"be8bc26b-fd3d-440b-8e9f-d698a0623be3" = true
+
+# can reverse dominoes
+"99e615c6-c059-401c-9e87-ad7af11fea5c" = true
+
+# can't be chained
+"51f0c291-5d43-40c5-b316-0429069528c9" = true
+
+# disconnected - simple
+"9a75e078-a025-4c23-8c3a-238553657f39" = true
+
+# disconnected - double loop
+"0da0c7fe-d492-445d-b9ef-1f111f07a301" = true
+
+# disconnected - single isolated
+"b6087ff0-f555-4ea0-a71c-f9d707c5994a" = true
+
+# need backtrack
+"2174fbdc-8b48-4bac-9914-8090d06ef978" = true
+
+# separate loops
+"167bb480-dfd1-4318-a20d-4f90adb4a09f" = true
+
+# nine elements
+"cd061538-6046-45a7-ace9-6708fe8f6504" = true
diff --git a/exercises/etl/.meta/tests.toml b/exercises/etl/.meta/tests.toml
new file mode 100644
index 000000000..83821984d
--- /dev/null
+++ b/exercises/etl/.meta/tests.toml
@@ -0,0 +1,13 @@
+[canonical-tests]
+
+# single letter
+"78a7a9f9-4490-4a47-8ee9-5a38bb47d28f" = true
+
+# single score with multiple letters
+"60dbd000-451d-44c7-bdbb-97c73ac1f497" = true
+
+# multiple scores with multiple letters
+"f5c5de0c-301f-4fdd-a0e5-df97d4214f54" = true
+
+# multiple scores with differing numbers of letters
+"5db8ea89-ecb4-4dcd-902f-2b418cc87b9d" = true
diff --git a/exercises/food-chain/.meta/tests.toml b/exercises/food-chain/.meta/tests.toml
new file mode 100644
index 000000000..8431f722e
--- /dev/null
+++ b/exercises/food-chain/.meta/tests.toml
@@ -0,0 +1,35 @@
+[canonical-tests]
+
+# All tests other than "full song" are marked false here,
+# since the Haskell track made a deliberate choice to test the full song only.
+# https://github.com/exercism/haskell/pull/346
+
+# fly
+"751dce68-9412-496e-b6e8-855998c56166" = false
+
+# spider
+"6c56f861-0c5e-4907-9a9d-b2efae389379" = false
+
+# bird
+"3edf5f33-bef1-4e39-ae67-ca5eb79203fa" = false
+
+# cat
+"e866a758-e1ff-400e-9f35-f27f28cc288f" = false
+
+# dog
+"3f02c30e-496b-4b2a-8491-bc7e2953cafb" = false
+
+# goat
+"4b3fd221-01ea-46e0-825b-5734634fbc59" = false
+
+# cow
+"1b707da9-7001-4fac-941f-22ad9c7a65d4" = false
+
+# horse
+"3cb10d46-ae4e-4d2c-9296-83c9ffc04cdc" = false
+
+# multiple verses
+"22b863d5-17e4-4d1e-93e4-617329a5c050" = false
+
+# full song
+"e626b32b-745c-4101-bcbd-3b13456893db" = true
diff --git a/exercises/forth/.meta/tests.toml b/exercises/forth/.meta/tests.toml
new file mode 100644
index 000000000..68d39fc6a
--- /dev/null
+++ b/exercises/forth/.meta/tests.toml
@@ -0,0 +1,139 @@
+[canonical-tests]
+
+# numbers just get pushed onto the stack
+"9962203f-f00a-4a85-b404-8a8ecbcec09d" = true
+
+# can add two numbers
+"9e69588e-a3d8-41a3-a371-ea02206c1e6e" = true
+
+# errors if there is nothing on the stack
+"52336dd3-30da-4e5c-8523-bdf9a3427657" = true
+
+# errors if there is only one value on the stack
+"06efb9a4-817a-435e-b509-06166993c1b8" = true
+
+# can subtract two numbers
+"09687c99-7bbc-44af-8526-e402f997ccbf" = true
+
+# errors if there is nothing on the stack
+"5d63eee2-1f7d-4538-b475-e27682ab8032" = true
+
+# errors if there is only one value on the stack
+"b3cee1b2-9159-418a-b00d-a1bb3765c23b" = true
+
+# can multiply two numbers
+"5df0ceb5-922e-401f-974d-8287427dbf21" = true
+
+# errors if there is nothing on the stack
+"9e004339-15ac-4063-8ec1-5720f4e75046" = true
+
+# errors if there is only one value on the stack
+"8ba4b432-9f94-41e0-8fae-3b3712bd51b3" = true
+
+# can divide two numbers
+"e74c2204-b057-4cff-9aa9-31c7c97a93f5" = true
+
+# performs integer division
+"54f6711c-4b14-4bb0-98ad-d974a22c4620" = true
+
+# errors if dividing by zero
+"a5df3219-29b4-4d2f-b427-81f82f42a3f1" = true
+
+# errors if there is nothing on the stack
+"1d5bb6b3-6749-4e02-8a79-b5d4d334cb8a" = true
+
+# errors if there is only one value on the stack
+"d5547f43-c2ff-4d5c-9cb0-2a4f6684c20d" = true
+
+# addition and subtraction
+"ee28d729-6692-4a30-b9be-0d830c52a68c" = true
+
+# multiplication and division
+"40b197da-fa4b-4aca-a50b-f000d19422c1" = true
+
+# copies a value on the stack
+"c5758235-6eef-4bf6-ab62-c878e50b9957" = true
+
+# copies the top value on the stack
+"f6889006-5a40-41e7-beb3-43b09e5a22f4" = true
+
+# errors if there is nothing on the stack
+"40b7569c-8401-4bd4-a30d-9adf70d11bc4" = true
+
+# removes the top value on the stack if it is the only one
+"1971da68-1df2-4569-927a-72bf5bb7263c" = true
+
+# removes the top value on the stack if it is not the only one
+"8929d9f2-4a78-4e0f-90ad-be1a0f313fd9" = true
+
+# errors if there is nothing on the stack
+"6dd31873-6dd7-4cb8-9e90-7daa33ba045c" = true
+
+# swaps the top two values on the stack if they are the only ones
+"3ee68e62-f98a-4cce-9e6c-8aae6c65a4e3" = true
+
+# swaps the top two values on the stack if they are not the only ones
+"8ce869d5-a503-44e4-ab55-1da36816ff1c" = true
+
+# errors if there is nothing on the stack
+"74ba5b2a-b028-4759-9176-c5c0e7b2b154" = true
+
+# errors if there is only one value on the stack
+"dd52e154-5d0d-4a5c-9e5d-73eb36052bc8" = true
+
+# copies the second element if there are only two
+"a2654074-ba68-4f93-b014-6b12693a8b50" = true
+
+# copies the second element if there are more than two
+"c5b51097-741a-4da7-8736-5c93fa856339" = true
+
+# errors if there is nothing on the stack
+"6e1703a6-5963-4a03-abba-02e77e3181fd" = true
+
+# errors if there is only one value on the stack
+"ee574dc4-ef71-46f6-8c6a-b4af3a10c45f" = true
+
+# can consist of built-in words
+"ed45cbbf-4dbf-4901-825b-54b20dbee53b" = true
+
+# execute in the right order
+"2726ea44-73e4-436b-bc2b-5ff0c6aa014b" = true
+
+# can override other user-defined words
+"9e53c2d0-b8ef-4ad8-b2c9-a559b421eb33" = true
+
+# can override built-in words
+"669db3f3-5bd6-4be0-83d1-618cd6e4984b" = true
+
+# can override built-in operators
+"588de2f0-c56e-4c68-be0b-0bb1e603c500" = true
+
+# can use different words with the same name
+"ac12aaaf-26c6-4a10-8b3c-1c958fa2914c" = true
+
+# can define word that uses word with the same name
+"53f82ef0-2750-4ccb-ac04-5d8c1aefabb1" = true
+
+# cannot redefine numbers
+"35958cee-a976-4a0f-9378-f678518fa322" = true
+
+# errors if executing a non-existent word
+"5180f261-89dd-491e-b230-62737e09806f" = true
+
+# DUP is case-insensitive
+"7b83bb2e-b0e8-461f-ad3b-96ee2e111ed6" = true
+
+# DROP is case-insensitive
+"339ed30b-f5b4-47ff-ab1c-67591a9cd336" = true
+
+# SWAP is case-insensitive
+"ee1af31e-1355-4b1b-bb95-f9d0b2961b87" = true
+
+# OVER is case-insensitive
+"acdc3a49-14c8-4cc2-945d-11edee6408fa" = true
+
+# user-defined words are case-insensitive
+"5934454f-a24f-4efc-9fdd-5794e5f0c23c" = true
+
+# definitions are case-insensitive
+"037d4299-195f-4be7-a46d-f07ca6280a06" = true
diff --git a/exercises/gigasecond/.meta/tests.toml b/exercises/gigasecond/.meta/tests.toml
new file mode 100644
index 000000000..53074f554
--- /dev/null
+++ b/exercises/gigasecond/.meta/tests.toml
@@ -0,0 +1,16 @@
+[canonical-tests]
+
+# date only specification of time
+"92fbe71c-ea52-4fac-bd77-be38023cacf7" = true
+
+# second test for date only specification of time
+"6d86dd16-6f7a-47be-9e58-bb9fb2ae1433" = true
+
+# third test for date only specification of time
+"77eb8502-2bca-4d92-89d9-7b39ace28dd5" = true
+
+# full time specified
+"c9d89a7d-06f8-4e28-a305-64f1b2abc693" = true
+
+# full time with day roll-over
+"09d4e30e-728a-4b52-9005-be44a58d9eba" = true
diff --git a/exercises/go-counting/.meta/tests.toml b/exercises/go-counting/.meta/tests.toml
new file mode 100644
index 000000000..cb45d53eb
--- /dev/null
+++ b/exercises/go-counting/.meta/tests.toml
@@ -0,0 +1,34 @@
+[canonical-tests]
+
+# Black corner territory on 5x5 board
+"94d0c01a-17d0-424c-aab5-2736d0da3939" = true
+
+# White center territory on 5x5 board
+"b33bec54-356a-485c-9c71-1142a9403213" = true
+
+# Open corner territory on 5x5 board
+"def7d124-422e-44ae-90e5-ceda09399bda" = true
+
+# A stone and not a territory on 5x5 board
+"57d79036-2618-47f4-aa87-56c06d362e3d" = true
+
+# Invalid because X is too low for 5x5 board
+"0c84f852-e032-4762-9010-99f6a001da96" = true
+
+# Invalid because X is too high for 5x5 board
+"6f867945-9b2c-4bdd-b23e-b55fe2069a68" = true
+
+# Invalid because Y is too low for 5x5 board
+"d67aaffd-fdf1-4e7f-b9e9-79897402b64a" = true
+
+# Invalid because Y is too high for 5x5 board
+"14f23c25-799e-4371-b3e5-777a2c30357a" = true
+
+# One territory is the whole board
+"37fb04b5-98c1-4b96-8c16-af2d13624afd" = true
+
+# Two territory rectangular board
+"9a1c59b7-234b-495a-8d60-638489f0fc0a" = true
+
+# Two region rectangular board
+"d1645953-1cd5-4221-af6f-8164f96249e1" = true
diff --git a/exercises/grade-school/.meta/tests.toml b/exercises/grade-school/.meta/tests.toml
new file mode 100644
index 000000000..bdbeb1586
--- /dev/null
+++ b/exercises/grade-school/.meta/tests.toml
@@ -0,0 +1,22 @@
+[canonical-tests]
+
+# Adding a student adds them to the sorted roster
+"6d0a30e4-1b4e-472e-8e20-c41702125667" = true
+
+# Adding more student adds them to the sorted roster
+"233be705-dd58-4968-889d-fb3c7954c9cc" = true
+
+# Adding students to different grades adds them to the same sorted roster
+"75a51579-d1d7-407c-a2f8-2166e984e8ab" = true
+
+# Roster returns an empty list if there are no students enrolled
+"a3f0fb58-f240-4723-8ddc-e644666b85cc" = true
+
+# Student names with grades are displayed in the same sorted roster
+"180a8ff9-5b94-43fc-9db1-d46b4a8c93b6" = true
+
+# Grade returns the students in that grade in alphabetical order
+"1bfbcef1-e4a3-49e8-8d22-f6f9f386187e" = true
+
+# Grade returns an empty list if there are no students in that grade
+"5e67aa3c-a3c6-4407-a183-d8fe59cd1630" = true
diff --git a/exercises/grains/.meta/tests.toml b/exercises/grains/.meta/tests.toml
new file mode 100644
index 000000000..9a160e8ef
--- /dev/null
+++ b/exercises/grains/.meta/tests.toml
@@ -0,0 +1,34 @@
+[canonical-tests]
+
+# 1
+"9fbde8de-36b2-49de-baf2-cd42d6f28405" = true
+
+# 2
+"ee1f30c2-01d8-4298-b25d-c677331b5e6d" = true
+
+# 3
+"10f45584-2fc3-4875-8ec6-666065d1163b" = true
+
+# 4
+"a7cbe01b-36f4-4601-b053-c5f6ae055170" = true
+
+# 16
+"c50acc89-8535-44e4-918f-b848ad2817d4" = true
+
+# 32
+"acd81b46-c2ad-4951-b848-80d15ed5a04f" = true
+
+# 64
+"c73b470a-5efb-4d53-9ac6-c5f6487f227b" = true
+
+# square 0 raises an exception
+"1d47d832-3e85-4974-9466-5bd35af484e3" = true
+
+# negative square raises an exception
+"61974483-eeb2-465e-be54-ca5dde366453" = true
+
+# square greater than 64 raises an exception
+"a95e4374-f32c-45a7-a10d-ffec475c012f" = true
+
+# returns the total number of grains on the board
+"6eb07385-3659-4b45-a6be-9dc474222750" = true
diff --git a/exercises/hamming/.meta/tests.toml b/exercises/hamming/.meta/tests.toml
new file mode 100644
index 000000000..f261259b0
--- /dev/null
+++ b/exercises/hamming/.meta/tests.toml
@@ -0,0 +1,28 @@
+[canonical-tests]
+
+# empty strands
+"f6dcb64f-03b0-4b60-81b1-3c9dbf47e887" = true
+
+# single letter identical strands
+"54681314-eee2-439a-9db0-b0636c656156" = true
+
+# single letter different strands
+"294479a3-a4c8-478f-8d63-6209815a827b" = true
+
+# long identical strands
+"9aed5f34-5693-4344-9b31-40c692fb5592" = true
+
+# long different strands
+"cd2273a5-c576-46c8-a52b-dee251c3e6e5" = true
+
+# disallow first strand longer
+"919f8ef0-b767-4d1b-8516-6379d07fcb28" = true
+
+# disallow second strand longer
+"8a2d4ed0-ead5-4fdd-924d-27c4cf56e60e" = true
+
+# disallow left empty strand
+"5dce058b-28d4-4ca7-aa64-adfe4e17784c" = true
+
+# disallow right empty strand
+"38826d4b-16fb-4639-ac3e-ba027dec8b5f" = true
diff --git a/exercises/hello-world/.meta/tests.toml b/exercises/hello-world/.meta/tests.toml
new file mode 100644
index 000000000..e64aebebc
--- /dev/null
+++ b/exercises/hello-world/.meta/tests.toml
@@ -0,0 +1,4 @@
+[canonical-tests]
+
+# Say Hi!
+"af9ffe10-dc13-42d8-a742-e7bdafac449d" = true
diff --git a/exercises/house/.meta/tests.toml b/exercises/house/.meta/tests.toml
new file mode 100644
index 000000000..228c08e96
--- /dev/null
+++ b/exercises/house/.meta/tests.toml
@@ -0,0 +1,47 @@
+[canonical-tests]
+
+# All tests other than "full rhyme" are marked false here,
+# since the Haskell track made a deliberate choice to test the full song only.
+# https://github.com/exercism/haskell/pull/348
+
+# verse one - the house that jack built
+"28a540ff-f765-4348-9d57-ae33f25f41f2" = false
+
+# verse two - the malt that lay
+"ebc825ac-6e2b-4a5e-9afd-95732191c8da" = false
+
+# verse three - the rat that ate
+"1ed8bb0f-edb8-4bd1-b6d4-b64754fe4a60" = false
+
+# verse four - the cat that killed
+"64b0954e-8b7d-4d14-aad0-d3f6ce297a30" = false
+
+# verse five - the dog that worried
+"1e8d56bc-fe31-424d-9084-61e6111d2c82" = false
+
+# verse six - the cow with the crumpled horn
+"6312dc6f-ab0a-40c9-8a55-8d4e582beac4" = false
+
+# verse seven - the maiden all forlorn
+"68f76d18-6e19-4692-819c-5ff6a7f92feb" = false
+
+# verse eight - the man all tattered and torn
+"73872564-2004-4071-b51d-2e4326096747" = false
+
+# verse nine - the priest all shaven and shorn
+"0d53d743-66cb-4351-a173-82702f3338c9" = false
+
+# verse 10 - the rooster that crowed in the morn
+"452f24dc-8fd7-4a82-be1a-3b4839cfeb41" = false
+
+# verse 11 - the farmer sowing his corn
+"97176f20-2dd3-4646-ac72-cffced91ea26" = false
+
+# verse 12 - the horse and the hound and the horn
+"09824c29-6aad-4dcd-ac98-f61374a6a8b7" = false
+
+# multiple verses
+"d2b980d3-7851-49e1-97ab-1524515ec200" = false
+
+# full rhyme
+"0311d1d0-e085-4f23-8ae7-92406fb3e803" = true
diff --git a/exercises/isbn-verifier/.meta/tests.toml b/exercises/isbn-verifier/.meta/tests.toml
new file mode 100644
index 000000000..39d42f6e4
--- /dev/null
+++ b/exercises/isbn-verifier/.meta/tests.toml
@@ -0,0 +1,52 @@
+[canonical-tests]
+
+# valid isbn number
+"0caa3eac-d2e3-4c29-8df8-b188bc8c9292" = true
+
+# invalid isbn check digit
+"19f76b53-7c24-45f8-87b8-4604d0ccd248" = true
+
+# valid isbn number with a check digit of 10
+"4164bfee-fb0a-4a1c-9f70-64c6a1903dcd" = true
+
+# check digit is a character other than X
+"3ed50db1-8982-4423-a993-93174a20825c" = true
+
+# invalid character in isbn
+"c19ba0c4-014f-4dc3-a63f-ff9aefc9b5ec" = true
+
+# X is only valid as a check digit
+"28025280-2c39-4092-9719-f3234b89c627" = true
+
+# valid isbn without separating dashes
+"f6294e61-7e79-46b3-977b-f48789a4945b" = true
+
+# isbn without separating dashes and X as check digit
+"185ab99b-3a1b-45f3-aeec-b80d80b07f0b" = true
+
+# isbn without check digit and dashes
+"7725a837-ec8e-4528-a92a-d981dd8cf3e2" = true
+
+# too long isbn and no dashes
+"47e4dfba-9c20-46ed-9958-4d3190630bdf" = true
+
+# too short isbn
+"737f4e91-cbba-4175-95bf-ae630b41fb60" = true
+
+# isbn without check digit
+"5458a128-a9b6-4ff8-8afb-674e74567cef" = true
+
+# check digit of X should not be used for 0
+"70b6ad83-d0a2-4ca7-a4d5-a9ab731800f7" = true
+
+# empty isbn
+"94610459-55ab-4c35-9b93-ff6ea1a8e562" = true
+
+# input is 9 characters
+"7bff28d4-d770-48cc-80d6-b20b3a0fb46c" = true
+
+# invalid characters are not ignored
+"ed6e8d1b-382c-4081-8326-8b772c581fec" = true
+
+# input is too long but contains a valid isbn
+"fb5e48d8-7c03-4bfb-a088-b101df16fdc3" = true
diff --git a/exercises/isogram/.meta/tests.toml b/exercises/isogram/.meta/tests.toml
new file mode 100644
index 000000000..cfecd1ee2
--- /dev/null
+++ b/exercises/isogram/.meta/tests.toml
@@ -0,0 +1,40 @@
+[canonical-tests]
+
+# empty string
+"a0e97d2d-669e-47c7-8134-518a1e2c4555" = true
+
+# isogram with only lower case characters
+"9a001b50-f194-4143-bc29-2af5ec1ef652" = true
+
+# word with one duplicated character
+"8ddb0ca3-276e-4f8b-89da-d95d5bae78a4" = true
+
+# word with one duplicated character from the end of the alphabet
+"6450b333-cbc2-4b24-a723-0b459b34fe18" = true
+
+# longest reported english isogram
+"a15ff557-dd04-4764-99e7-02cc1a385863" = true
+
+# word with duplicated character in mixed case
+"f1a7f6c7-a42f-4915-91d7-35b2ea11c92e" = true
+
+# word with duplicated character in mixed case, lowercase first
+"14a4f3c1-3b47-4695-b645-53d328298942" = true
+
+# hypothetical isogrammic word with hyphen
+"423b850c-7090-4a8a-b057-97f1cadd7c42" = true
+
+# hypothetical word with duplicated character following hyphen
+"93dbeaa0-3c5a-45c2-8b25-428b8eacd4f2" = true
+
+# isogram with duplicated hyphen
+"36b30e5c-173f-49c6-a515-93a3e825553f" = true
+
+# made-up name that is an isogram
+"cdabafa0-c9f4-4c1f-b142-689c6ee17d93" = true
+
+# duplicated character in the middle
+"5fc61048-d74e-48fd-bc34-abfc21552d4d" = true
+
+# same first and last characters
+"310ac53d-8932-47bc-bbb4-b2b94f25a83e" = true
diff --git a/exercises/kindergarten-garden/.meta/tests.toml b/exercises/kindergarten-garden/.meta/tests.toml
new file mode 100644
index 000000000..ec946c699
--- /dev/null
+++ b/exercises/kindergarten-garden/.meta/tests.toml
@@ -0,0 +1,28 @@
+[canonical-tests]
+
+# garden with single student
+"1fc316ed-17ab-4fba-88ef-3ae78296b692" = true
+
+# different garden with single student
+"acd19dc1-2200-4317-bc2a-08f021276b40" = true
+
+# garden with two students
+"c376fcc8-349c-446c-94b0-903947315757" = true
+
+# second student's garden
+"2d620f45-9617-4924-9d27-751c80d17db9" = true
+
+# third student's garden
+"57712331-4896-4364-89f8-576421d69c44" = true
+
+# first student's garden
+"149b4290-58e1-40f2-8ae4-8b87c46e765b" = true
+
+# second student's garden
+"ba25dbbc-10bd-4a37-b18e-f89ecd098a5e" = true
+
+# second to last student's garden
+"6bb66df7-f433-41ab-aec2-3ead6e99f65b" = true
+
+# last student's garden
+"d7edec11-6488-418a-94e6-ed509e0fa7eb" = true
diff --git a/exercises/largest-series-product/.meta/tests.toml b/exercises/largest-series-product/.meta/tests.toml
new file mode 100644
index 000000000..6bd30c051
--- /dev/null
+++ b/exercises/largest-series-product/.meta/tests.toml
@@ -0,0 +1,46 @@
+[canonical-tests]
+
+# finds the largest product if span equals length
+"7c82f8b7-e347-48ee-8a22-f672323324d4" = true
+
+# can find the largest product of 2 with numbers in order
+"88523f65-21ba-4458-a76a-b4aaf6e4cb5e" = true
+
+# can find the largest product of 2
+"f1376b48-1157-419d-92c2-1d7e36a70b8a" = true
+
+# can find the largest product of 3 with numbers in order
+"46356a67-7e02-489e-8fea-321c2fa7b4a4" = true
+
+# can find the largest product of 3
+"a2dcb54b-2b8f-4993-92dd-5ce56dece64a" = true
+
+# can find the largest product of 5 with numbers in order
+"673210a3-33cd-4708-940b-c482d7a88f9d" = true
+
+# can get the largest product of a big number
+"02acd5a6-3bbf-46df-8282-8b313a80a7c9" = true
+
+# reports zero if the only digits are zero
+"76dcc407-21e9-424c-a98e-609f269622b5" = true
+
+# reports zero if all spans include zero
+"6ef0df9f-52d4-4a5d-b210-f6fae5f20e19" = true
+
+# rejects span longer than string length
+"5d81aaf7-4f67-4125-bf33-11493cc7eab7" = true
+
+# reports 1 for empty string and empty product (0 span)
+"06bc8b90-0c51-4c54-ac22-3ec3893a079e" = true
+
+# reports 1 for nonempty string and empty product (0 span)
+"3ec0d92e-f2e2-4090-a380-70afee02f4c0" = true
+
+# rejects empty string and nonzero span
+"6d96c691-4374-4404-80ee-2ea8f3613dd4" = true
+
+# rejects invalid character in digits
+"7a38f2d6-3c35-45f6-8d6f-12e6e32d4d74" = true
+
+# rejects negative span
+"5fe3c0e5-a945-49f2-b584-f0814b4dd1ef" = true
diff --git a/exercises/leap/.meta/tests.toml b/exercises/leap/.meta/tests.toml
new file mode 100644
index 000000000..8e23f5331
--- /dev/null
+++ b/exercises/leap/.meta/tests.toml
@@ -0,0 +1,28 @@
+[canonical-tests]
+
+# year not divisible by 4 in common year
+"6466b30d-519c-438e-935d-388224ab5223" = true
+
+# year divisible by 2, not divisible by 4 in common year
+"ac227e82-ee82-4a09-9eb6-4f84331ffdb0" = true
+
+# year divisible by 4, not divisible by 100 in leap year
+"4fe9b84c-8e65-489e-970b-856d60b8b78e" = true
+
+# year divisible by 4 and 5 is still a leap year
+"7fc6aed7-e63c-48f5-ae05-5fe182f60a5d" = true
+
+# year divisible by 100, not divisible by 400 in common year
+"78a7848f-9667-4192-ae53-87b30c9a02dd" = true
+
+# year divisible by 100 but not by 3 is still not a leap year
+"9d70f938-537c-40a6-ba19-f50739ce8bac" = true
+
+# year divisible by 400 in leap year
+"42ee56ad-d3e6-48f1-8e3f-c84078d916fc" = true
+
+# year divisible by 400 but not by 125 is still a leap year
+"57902c77-6fe9-40de-8302-587b5c27121e" = true
+
+# year divisible by 200, not divisible by 400 in common year
+"c30331f6-f9f6-4881-ad38-8ca8c12520c1" = true
diff --git a/exercises/list-ops/.meta/tests.toml b/exercises/list-ops/.meta/tests.toml
new file mode 100644
index 000000000..7c5aff115
--- /dev/null
+++ b/exercises/list-ops/.meta/tests.toml
@@ -0,0 +1,64 @@
+[canonical-tests]
+
+# empty lists
+"485b9452-bf94-40f7-a3db-c3cf4850066a" = true
+
+# list to empty list
+"2c894696-b609-4569-b149-8672134d340a" = true
+
+# non-empty lists
+"71dcf5eb-73ae-4a0e-b744-a52ee387922f" = true
+
+# empty list
+"28444355-201b-4af2-a2f6-5550227bde21" = true
+
+# list of lists
+"331451c1-9573-42a1-9869-2d06e3b389a9" = true
+
+# list of nested lists
+"d6ecd72c-197f-40c3-89a4-aa1f45827e09" = true
+
+# empty list
+"0524fba8-3e0f-4531-ad2b-f7a43da86a16" = true
+
+# non-empty list
+"88494bd5-f520-4edb-8631-88e415b62d24" = true
+
+# empty list
+"1cf0b92d-8d96-41d5-9c21-7b3c37cb6aad" = true
+
+# non-empty list
+"d7b8d2d9-2d16-44c4-9a19-6e5f237cb71e" = true
+
+# empty list
+"c0bc8962-30e2-4bec-9ae4-668b8ecd75aa" = true
+
+# non-empty list
+"11e71a95-e78b-4909-b8e4-60cdcaec0e91" = true
+
+# empty list
+"613b20b7-1873-4070-a3a6-70ae5f50d7cc" = true
+
+# direction independent function applied to non-empty list
+"e56df3eb-9405-416a-b13a-aabb4c3b5194" = true
+
+# direction dependent function applied to non-empty list
+"d2cf5644-aee1-4dfc-9b88-06896676fe27" = true
+
+# empty list
+"aeb576b9-118e-4a57-a451-db49fac20fdc" = true
+
+# direction independent function applied to non-empty list
+"c4b64e58-313e-4c47-9c68-7764964efb8e" = true
+
+# direction dependent function applied to non-empty list
+"be396a53-c074-4db3-8dd6-f7ed003cce7c" = true
+
+# empty list
+"94231515-050e-4841-943d-d4488ab4ee30" = true
+
+# non-empty list
+"fcc03d1e-42e0-4712-b689-d54ad761f360" = true
+
+# list of lists is not flattened
+"40872990-b5b8-4cb8-9085-d91fc0d05d26" = true
diff --git a/exercises/luhn/.meta/tests.toml b/exercises/luhn/.meta/tests.toml
new file mode 100644
index 000000000..2b441c6ff
--- /dev/null
+++ b/exercises/luhn/.meta/tests.toml
@@ -0,0 +1,55 @@
+[canonical-tests]
+
+# single digit strings can not be valid
+"792a7082-feb7-48c7-b88b-bbfec160865e" = true
+
+# a single zero is invalid
+"698a7924-64d4-4d89-8daa-32e1aadc271e" = true
+
+# a simple valid SIN that remains valid if reversed
+"73c2f62b-9b10-4c9f-9a04-83cee7367965" = true
+
+# a simple valid SIN that becomes invalid if reversed
+"9369092e-b095-439f-948d-498bd076be11" = true
+
+# a valid Canadian SIN
+"8f9f2350-1faf-4008-ba84-85cbb93ffeca" = true
+
+# invalid Canadian SIN
+"1cdcf269-6560-44fc-91f6-5819a7548737" = true
+
+# invalid credit card
+"656c48c1-34e8-4e60-9a5a-aad8a367810a" = true
+
+# invalid long number with an even remainder
+"20e67fad-2121-43ed-99a8-14b5b856adb9" = true
+
+# valid number with an even number of digits
+"ad2a0c5f-84ed-4e5b-95da-6011d6f4f0aa" = true
+
+# valid number with an odd number of spaces
+"ef081c06-a41f-4761-8492-385e13c8202d" = true
+
+# valid strings with a non-digit added at the end become invalid
+"bef66f64-6100-4cbb-8f94-4c9713c5e5b2" = true
+
+# valid strings with punctuation included become invalid
+"2177e225-9ce7-40f6-b55d-fa420e62938e" = true
+
+# valid strings with symbols included become invalid
+"ebf04f27-9698-45e1-9afe-7e0851d0fe8d" = true
+
+# single zero with space is invalid
+"08195c5e-ce7f-422c-a5eb-3e45fece68ba" = true
+
+# more than a single zero is valid
+"12e63a3c-f866-4a79-8c14-b359fc386091" = true
+
+# input digit 9 is correctly converted to output digit 9
+"ab56fa80-5de8-4735-8a4a-14dae588663e" = true
+
+# using ascii value for non-doubled non-digit isn't allowed
+"39a06a5a-5bad-4e0f-b215-b042d46209b1" = true
+
+# using ascii value for doubled non-digit isn't allowed
+"f94cf191-a62f-4868-bc72-7253114aa157" = true
diff --git a/exercises/matching-brackets/.meta/tests.toml b/exercises/matching-brackets/.meta/tests.toml
new file mode 100644
index 000000000..98309c731
--- /dev/null
+++ b/exercises/matching-brackets/.meta/tests.toml
@@ -0,0 +1,52 @@
+[canonical-tests]
+
+# paired square brackets
+"81ec11da-38dd-442a-bcf9-3de7754609a5" = true
+
+# empty string
+"287f0167-ac60-4b64-8452-a0aa8f4e5238" = true
+
+# unpaired brackets
+"6c3615a3-df01-4130-a731-8ef5f5d78dac" = true
+
+# wrong ordered brackets
+"9d414171-9b98-4cac-a4e5-941039a97a77" = true
+
+# wrong closing bracket
+"f0f97c94-a149-4736-bc61-f2c5148ffb85" = true
+
+# paired with whitespace
+"754468e0-4696-4582-a30e-534d47d69756" = true
+
+# partially paired brackets
+"ba84f6ee-8164-434a-9c3e-b02c7f8e8545" = true
+
+# simple nested brackets
+"3c86c897-5ff3-4a2b-ad9b-47ac3a30651d" = true
+
+# several paired brackets
+"2d137f2c-a19e-4993-9830-83967a2d4726" = true
+
+# paired and nested brackets
+"2e1f7b56-c137-4c92-9781-958638885a44" = true
+
+# unopened closing brackets
+"84f6233b-e0f7-4077-8966-8085d295c19b" = true
+
+# unpaired and nested brackets
+"9b18c67d-7595-4982-b2c5-4cb949745d49" = true
+
+# paired and wrong nested brackets
+"a0205e34-c2ac-49e6-a88a-899508d7d68e" = true
+
+# paired and incomplete brackets
+"ef47c21b-bcfd-4998-844c-7ad5daad90a8" = true
+
+# too many closing brackets
+"a4675a40-a8be-4fc2-bc47-2a282ce6edbe" = true
+
+# math expression
+"99255f93-261b-4435-a352-02bdecc9bdf2" = true
+
+# complex latex expression
+"8e357d79-f302-469a-8515-2561877256a1" = true
diff --git a/exercises/matrix/.meta/tests.toml b/exercises/matrix/.meta/tests.toml
new file mode 100644
index 000000000..986d6dd1c
--- /dev/null
+++ b/exercises/matrix/.meta/tests.toml
@@ -0,0 +1,25 @@
+[canonical-tests]
+
+# extract row from one number matrix
+"ca733dab-9d85-4065-9ef6-a880a951dafd" = true
+
+# can extract row
+"5c93ec93-80e1-4268-9fc2-63bc7d23385c" = true
+
+# extract row where numbers have different widths
+"2f1aad89-ad0f-4bd2-9919-99a8bff0305a" = true
+
+# can extract row from non-square matrix with no corresponding column
+"68f7f6ba-57e2-4e87-82d0-ad09889b5204" = true
+
+# extract column from one number matrix
+"e8c74391-c93b-4aed-8bfe-f3c9beb89ebb" = true
+
+# can extract column
+"7136bdbd-b3dc-48c4-a10c-8230976d3727" = true
+
+# can extract column from non-square matrix with no corresponding row
+"ad64f8d7-bba6-4182-8adf-0c14de3d0eca" = true
+
+# extract column where numbers have different widths
+"9eddfa5c-8474-440e-ae0a-f018c2a0dd89" = true
diff --git a/exercises/meetup/.meta/tests.toml b/exercises/meetup/.meta/tests.toml
new file mode 100644
index 000000000..45366ba1b
--- /dev/null
+++ b/exercises/meetup/.meta/tests.toml
@@ -0,0 +1,286 @@
+[canonical-tests]
+
+# monteenth of May 2013
+"d7f8eadd-d4fc-46ee-8a20-e97bd3fd01c8" = true
+
+# monteenth of August 2013
+"f78373d1-cd53-4a7f-9d37-e15bf8a456b4" = true
+
+# monteenth of September 2013
+"8c78bea7-a116-425b-9c6b-c9898266d92a" = true
+
+# tuesteenth of March 2013
+"cfef881b-9dc9-4d0b-8de4-82d0f39fc271" = true
+
+# tuesteenth of April 2013
+"69048961-3b00-41f9-97ee-eb6d83a8e92b" = true
+
+# tuesteenth of August 2013
+"d30bade8-3622-466a-b7be-587414e0caa6" = true
+
+# wednesteenth of January 2013
+"8db4b58b-92f3-4687-867b-82ee1a04f851" = true
+
+# wednesteenth of February 2013
+"6c27a2a2-28f8-487f-ae81-35d08c4664f7" = true
+
+# wednesteenth of June 2013
+"008a8674-1958-45b5-b8e6-c2c9960d973a" = true
+
+# thursteenth of May 2013
+"e4abd5e3-57cb-4091-8420-d97e955c0dbd" = true
+
+# thursteenth of June 2013
+"85da0b0f-eace-4297-a6dd-63588d5055b4" = true
+
+# thursteenth of September 2013
+"ecf64f9b-8413-489b-bf6e-128045f70bcc" = true
+
+# friteenth of April 2013
+"ac4e180c-7d0a-4d3d-b05f-f564ebb584ca" = true
+
+# friteenth of August 2013
+"b79101c7-83ad-4f8f-8ec8-591683296315" = true
+
+# friteenth of September 2013
+"6ed38b9f-0072-4901-bd97-7c8b8b0ef1b8" = true
+
+# saturteenth of February 2013
+"dfae03ed-9610-47de-a632-655ab01e1e7c" = true
+
+# saturteenth of April 2013
+"ec02e3e1-fc72-4a3c-872f-a53fa8ab358e" = true
+
+# saturteenth of October 2013
+"d983094b-7259-4195-b84e-5d09578c89d9" = true
+
+# sunteenth of May 2013
+"d84a2a2e-f745-443a-9368-30051be60c2e" = true
+
+# sunteenth of June 2013
+"0e64bc53-92a3-4f61-85b2-0b7168c7ce5a" = true
+
+# sunteenth of October 2013
+"de87652c-185e-4854-b3ae-04cf6150eead" = true
+
+# first Monday of March 2013
+"2cbfd0f5-ba3a-46da-a8cc-0fe4966d3411" = true
+
+# first Monday of April 2013
+"a6168c7c-ed95-4bb3-8f92-c72575fc64b0" = true
+
+# first Tuesday of May 2013
+"1bfc620f-1c54-4bbd-931f-4a1cd1036c20" = true
+
+# first Tuesday of June 2013
+"12959c10-7362-4ca0-a048-50cf1c06e3e2" = true
+
+# first Wednesday of July 2013
+"1033dc66-8d0b-48a1-90cb-270703d59d1d" = true
+
+# first Wednesday of August 2013
+"b89185b9-2f32-46f4-a602-de20b09058f6" = true
+
+# first Thursday of September 2013
+"53aedc4d-b2c8-4dfb-abf7-a8dc9cdceed5" = true
+
+# first Thursday of October 2013
+"b420a7e3-a94c-4226-870a-9eb3a92647f0" = true
+
+# first Friday of November 2013
+"61df3270-28b4-4713-bee2-566fa27302ca" = true
+
+# first Friday of December 2013
+"cad33d4d-595c-412f-85cf-3874c6e07abf" = true
+
+# first Saturday of January 2013
+"a2869b52-5bba-44f0-a863-07bd1f67eadb" = true
+
+# first Saturday of February 2013
+"3585315a-d0db-4ea1-822e-0f22e2a645f5" = true
+
+# first Sunday of March 2013
+"c49e9bd9-8ccf-4cf2-947a-0ccd4e4f10b1" = true
+
+# first Sunday of April 2013
+"1513328b-df53-4714-8677-df68c4f9366c" = true
+
+# second Monday of March 2013
+"49e083af-47ec-4018-b807-62ef411efed7" = true
+
+# second Monday of April 2013
+"6cb79a73-38fe-4475-9101-9eec36cf79e5" = true
+
+# second Tuesday of May 2013
+"4c39b594-af7e-4445-aa03-bf4f8effd9a1" = true
+
+# second Tuesday of June 2013
+"41b32c34-2e39-40e3-b790-93539aaeb6dd" = true
+
+# second Wednesday of July 2013
+"90a160c5-b5d9-4831-927f-63a78b17843d" = true
+
+# second Wednesday of August 2013
+"23b98ce7-8dd5-41a1-9310-ef27209741cb" = true
+
+# second Thursday of September 2013
+"447f1960-27ca-4729-bc3f-f36043f43ed0" = true
+
+# second Thursday of October 2013
+"c9aa2687-300c-4e79-86ca-077849a81bde" = true
+
+# second Friday of November 2013
+"a7e11ef3-6625-4134-acda-3e7195421c09" = true
+
+# second Friday of December 2013
+"8b420e5f-9290-4106-b5ae-022f3e2a3e41" = true
+
+# second Saturday of January 2013
+"80631afc-fc11-4546-8b5f-c12aaeb72b4f" = true
+
+# second Saturday of February 2013
+"e34d43ac-f470-44c2-aa5f-e97b78ecaf83" = true
+
+# second Sunday of March 2013
+"a57d59fd-1023-47ad-b0df-a6feb21b44fc" = true
+
+# second Sunday of April 2013
+"a829a8b0-abdd-4ad1-b66c-5560d843c91a" = true
+
+# third Monday of March 2013
+"501a8a77-6038-4fc0-b74c-33634906c29d" = true
+
+# third Monday of April 2013
+"49e4516e-cf32-4a58-8bbc-494b7e851c92" = true
+
+# third Tuesday of May 2013
+"4db61095-f7c7-493c-85f1-9996ad3012c7" = true
+
+# third Tuesday of June 2013
+"714fc2e3-58d0-4b91-90fd-61eefd2892c0" = true
+
+# third Wednesday of July 2013
+"b08a051a-2c80-445b-9b0e-524171a166d1" = true
+
+# third Wednesday of August 2013
+"80bb9eff-3905-4c61-8dc9-bb03016d8ff8" = true
+
+# third Thursday of September 2013
+"fa52a299-f77f-4784-b290-ba9189fbd9c9" = true
+
+# third Thursday of October 2013
+"f74b1bc6-cc5c-4bf1-ba69-c554a969eb38" = true
+
+# third Friday of November 2013
+"8900f3b0-801a-466b-a866-f42d64667abd" = true
+
+# third Friday of December 2013
+"538ac405-a091-4314-9ccd-920c4e38e85e" = true
+
+# third Saturday of January 2013
+"244db35c-2716-4fa0-88ce-afd58e5cf910" = true
+
+# third Saturday of February 2013
+"dd28544f-f8fa-4f06-9bcd-0ad46ce68e9e" = true
+
+# third Sunday of March 2013
+"be71dcc6-00d2-4b53-a369-cbfae55b312f" = true
+
+# third Sunday of April 2013
+"b7d2da84-4290-4ee6-a618-ee124ae78be7" = true
+
+# fourth Monday of March 2013
+"4276dc06-a1bd-4fc2-b6c2-625fee90bc88" = true
+
+# fourth Monday of April 2013
+"ddbd7976-2deb-4250-8a38-925ac1a8e9a2" = true
+
+# fourth Tuesday of May 2013
+"eb714ef4-1656-47cc-913c-844dba4ebddd" = true
+
+# fourth Tuesday of June 2013
+"16648435-7937-4d2d-b118-c3e38fd084bd" = true
+
+# fourth Wednesday of July 2013
+"de062bdc-9484-437a-a8c5-5253c6f6785a" = true
+
+# fourth Wednesday of August 2013
+"c2ce6821-169c-4832-8d37-690ef5d9514a" = true
+
+# fourth Thursday of September 2013
+"d462c631-2894-4391-a8e3-dbb98b7a7303" = true
+
+# fourth Thursday of October 2013
+"9ff1f7b6-1b72-427d-9ee9-82b5bb08b835" = true
+
+# fourth Friday of November 2013
+"83bae8ba-1c49-49bc-b632-b7c7e1d7e35f" = true
+
+# fourth Friday of December 2013
+"de752d2a-a95e-48d2-835b-93363dac3710" = true
+
+# fourth Saturday of January 2013
+"eedd90ad-d581-45db-8312-4c6dcf9cf560" = true
+
+# fourth Saturday of February 2013
+"669fedcd-912e-48c7-a0a1-228b34af91d0" = true
+
+# fourth Sunday of March 2013
+"648e3849-ea49-44a5-a8a3-9f2a43b3bf1b" = true
+
+# fourth Sunday of April 2013
+"f81321b3-99ab-4db6-9267-69c5da5a7823" = true
+
+# last Monday of March 2013
+"1af5e51f-5488-4548-aee8-11d7d4a730dc" = true
+
+# last Monday of April 2013
+"f29999f2-235e-4ec7-9dab-26f137146526" = true
+
+# last Tuesday of May 2013
+"31b097a0-508e-48ac-bf8a-f63cdcf6dc41" = true
+
+# last Tuesday of June 2013
+"8c022150-0bb5-4a1f-80f9-88b2e2abcba4" = true
+
+# last Wednesday of July 2013
+"0e762194-672a-4bdf-8a37-1e59fdacef12" = true
+
+# last Wednesday of August 2013
+"5016386a-f24e-4bd7-b439-95358f491b66" = true
+
+# last Thursday of September 2013
+"12ead1a5-cdf9-4192-9a56-2229e93dd149" = true
+
+# last Thursday of October 2013
+"7db89e11-7fbe-4e57-ae3c-0f327fbd7cc7" = true
+
+# last Friday of November 2013
+"e47a739e-b979-460d-9c8a-75c35ca2290b" = true
+
+# last Friday of December 2013
+"5bed5aa9-a57a-4e5d-8997-2cc796a5b0ec" = true
+
+# last Saturday of January 2013
+"61e54cba-76f3-4772-a2b1-bf443fda2137" = true
+
+# last Saturday of February 2013
+"8b6a737b-2fa9-444c-b1a2-80ce7a2ec72f" = true
+
+# last Sunday of March 2013
+"0b63e682-f429-4d19-9809-4a45bd0242dc" = true
+
+# last Sunday of April 2013
+"5232307e-d3e3-4afc-8ba6-4084ad987c00" = true
+
+# last Wednesday of February 2012
+"0bbd48e8-9773-4e81-8e71-b9a51711e3c5" = true
+
+# last Wednesday of December 2014
+"fe0936de-7eee-4a48-88dd-66c07ab1fefc" = true
+
+# last Sunday of February 2015
+"2ccf2488-aafc-4671-a24e-2b6effe1b0e2" = true
+
+# first Friday of December 2012
+"00c3ce9f-cf36-4b70-90d8-92b32be6830e" = true
diff --git a/exercises/minesweeper/.meta/tests.toml b/exercises/minesweeper/.meta/tests.toml
new file mode 100644
index 000000000..e91b16d0b
--- /dev/null
+++ b/exercises/minesweeper/.meta/tests.toml
@@ -0,0 +1,37 @@
+[canonical-tests]
+
+# no rows
+"0c5ec4bd-dea7-4138-8651-1203e1cb9f44" = true
+
+# no columns
+"650ac4c0-ad6b-4b41-acde-e4ea5852c3b8" = true
+
+# no mines
+"6fbf8f6d-a03b-42c9-9a58-b489e9235478" = true
+
+# minefield with only mines
+"61aff1c4-fb31-4078-acad-cd5f1e635655" = true
+
+# mine surrounded by spaces
+"84167147-c504-4896-85d7-246b01dea7c5" = true
+
+# space surrounded by mines
+"cb878f35-43e3-4c9d-93d9-139012cccc4a" = true
+
+# horizontal line
+"7037f483-ddb4-4b35-b005-0d0f4ef4606f" = true
+
+# horizontal line, mines at edges
+"e359820f-bb8b-4eda-8762-47b64dba30a6" = true
+
+# vertical line
+"c5198b50-804f-47e9-ae02-c3b42f7ce3ab" = true
+
+# vertical line, mines at edges
+"0c79a64d-703d-4660-9e90-5adfa5408939" = true
+
+# cross
+"4b098563-b7f3-401c-97c6-79dd1b708f34" = true
+
+# large minefield
+"04a260f1-b40a-4e89-839e-8dd8525abe0e" = true
diff --git a/exercises/nth-prime/.meta/tests.toml b/exercises/nth-prime/.meta/tests.toml
new file mode 100644
index 000000000..62e0316a3
--- /dev/null
+++ b/exercises/nth-prime/.meta/tests.toml
@@ -0,0 +1,16 @@
+[canonical-tests]
+
+# first prime
+"75c65189-8aef-471a-81de-0a90c728160c" = true
+
+# second prime
+"2c38804c-295f-4701-b728-56dea34fd1a0" = true
+
+# sixth prime
+"56692534-781e-4e8c-b1f9-3e82c1640259" = true
+
+# big prime
+"fce1e979-0edb-412d-93aa-2c744e8f50ff" = true
+
+# there is no zeroth prime
+"bd0a9eae-6df7-485b-a144-80e13c7d55b2" = true
diff --git a/exercises/nucleotide-count/.meta/tests.toml b/exercises/nucleotide-count/.meta/tests.toml
new file mode 100644
index 000000000..f0a57da7d
--- /dev/null
+++ b/exercises/nucleotide-count/.meta/tests.toml
@@ -0,0 +1,16 @@
+[canonical-tests]
+
+# empty strand
+"3e5c30a8-87e2-4845-a815-a49671ade970" = true
+
+# can count one nucleotide in single-character input
+"a0ea42a6-06d9-4ac6-828c-7ccaccf98fec" = true
+
+# strand with repeated nucleotide
+"eca0d565-ed8c-43e7-9033-6cefbf5115b5" = true
+
+# strand with multiple nucleotides
+"40a45eac-c83f-4740-901a-20b22d15a39f" = true
+
+# strand with invalid nucleotides
+"b4c47851-ee9e-4b0a-be70-a86e343bd851" = true
diff --git a/exercises/ocr-numbers/.meta/tests.toml b/exercises/ocr-numbers/.meta/tests.toml
new file mode 100644
index 000000000..ac7601407
--- /dev/null
+++ b/exercises/ocr-numbers/.meta/tests.toml
@@ -0,0 +1,52 @@
+[canonical-tests]
+
+# Recognizes 0
+"5ee54e1a-b554-4bf3-a056-9a7976c3f7e8" = true
+
+# Recognizes 1
+"027ada25-17fd-4d78-aee6-35a19623639d" = true
+
+# Unreadable but correctly sized inputs return ?
+"3cce2dbd-01d9-4f94-8fae-419a822e89bb" = true
+
+# Input with a number of lines that is not a multiple of four raises an error
+"cb19b733-4e36-4cf9-a4a1-6e6aac808b9a" = true
+
+# Input with a number of columns that is not a multiple of three raises an error
+"235f7bd1-991b-4587-98d4-84206eec4cc6" = true
+
+# Recognizes 110101100
+"4a841794-73c9-4da9-a779-1f9837faff66" = true
+
+# Garbled numbers in a string are replaced with ?
+"70c338f9-85b1-4296-a3a8-122901cdfde8" = true
+
+# Recognizes 2
+"ea494ff4-3610-44d7-ab7e-72fdef0e0802" = true
+
+# Recognizes 3
+"1acd2c00-412b-4268-93c2-bd7ff8e05a2c" = true
+
+# Recognizes 4
+"eaec6a15-be17-4b6d-b895-596fae5d1329" = true
+
+# Recognizes 5
+"440f397a-f046-4243-a6ca-81ab5406c56e" = true
+
+# Recognizes 6
+"f4c9cf6a-f1e2-4878-bfc3-9b85b657caa0" = true
+
+# Recognizes 7
+"e24ebf80-c611-41bb-a25a-ac2c0f232df5" = true
+
+# Recognizes 8
+"b79cad4f-e264-4818-9d9e-77766792e233" = true
+
+# Recognizes 9
+"5efc9cfc-9227-4688-b77d-845049299e66" = true
+
+# Recognizes string of decimal numbers
+"f60cb04a-42be-494e-a535-3451c8e097a4" = true
+
+# Numbers separated by empty lines are recognized. Lines are joined by commas.
+"b73ecf8b-4423-4b36-860d-3710bdb8a491" = true
diff --git a/exercises/palindrome-products/.meta/tests.toml b/exercises/palindrome-products/.meta/tests.toml
new file mode 100644
index 000000000..54b7736ef
--- /dev/null
+++ b/exercises/palindrome-products/.meta/tests.toml
@@ -0,0 +1,37 @@
+[canonical-tests]
+
+# finds the smallest palindrome from single digit factors
+"5cff78fe-cf02-459d-85c2-ce584679f887" = true
+
+# finds the largest palindrome from single digit factors
+"0853f82c-5fc4-44ae-be38-fadb2cced92d" = true
+
+# find the smallest palindrome from double digit factors
+"66c3b496-bdec-4103-9129-3fcb5a9063e1" = true
+
+# find the largest palindrome from double digit factors
+"a10682ae-530a-4e56-b89d-69664feafe53" = true
+
+# find smallest palindrome from triple digit factors
+"cecb5a35-46d1-4666-9719-fa2c3af7499d" = true
+
+# find the largest palindrome from triple digit factors
+"edab43e1-c35f-4ea3-8c55-2f31dddd92e5" = true
+
+# find smallest palindrome from four digit factors
+"4f802b5a-9d74-4026-a70f-b53ff9234e4e" = true
+
+# find the largest palindrome from four digit factors
+"787525e0-a5f9-40f3-8cb2-23b52cf5d0be" = true
+
+# empty result for smallest if no palindrome in the range
+"58fb1d63-fddb-4409-ab84-a7a8e58d9ea0" = true
+
+# empty result for largest if no palindrome in the range
+"9de9e9da-f1d9-49a5-8bfc-3d322efbdd02" = true
+
+# error result for smallest if min is more than max
+"12e73aac-d7ee-4877-b8aa-2aa3dcdb9f8a" = true
+
+# error result for largest if min is more than max
+"eeeb5bff-3f47-4b1e-892f-05829277bd74" = true
diff --git a/exercises/pangram/.meta/tests.toml b/exercises/pangram/.meta/tests.toml
new file mode 100644
index 000000000..75fba6dd6
--- /dev/null
+++ b/exercises/pangram/.meta/tests.toml
@@ -0,0 +1,31 @@
+[canonical-tests]
+
+# empty sentence
+"64f61791-508e-4f5c-83ab-05de042b0149" = true
+
+# perfect lower case
+"74858f80-4a4d-478b-8a5e-c6477e4e4e84" = true
+
+# only lower case
+"61288860-35ca-4abe-ba08-f5df76ecbdcd" = true
+
+# missing the letter 'x'
+"6564267d-8ac5-4d29-baf2-e7d2e304a743" = true
+
+# missing the letter 'h'
+"c79af1be-d715-4cdb-a5f2-b2fa3e7e0de0" = true
+
+# with underscores
+"d835ec38-bc8f-48e4-9e36-eb232427b1df" = true
+
+# with numbers
+"8cc1e080-a178-4494-b4b3-06982c9be2a8" = true
+
+# missing letters replaced by numbers
+"bed96b1c-ff95-45b8-9731-fdbdcb6ede9a" = true
+
+# mixed case and punctuation
+"938bd5d8-ade5-40e2-a2d9-55a338a01030" = true
+
+# case insensitive
+"2577bf54-83c8-402d-a64b-a2c0f7bb213a" = true
diff --git a/exercises/pascals-triangle/.meta/tests.toml b/exercises/pascals-triangle/.meta/tests.toml
new file mode 100644
index 000000000..879944dbe
--- /dev/null
+++ b/exercises/pascals-triangle/.meta/tests.toml
@@ -0,0 +1,25 @@
+[canonical-tests]
+
+# zero rows
+"9920ce55-9629-46d5-85d6-4201f4a4234d" = true
+
+# single row
+"70d643ce-a46d-4e93-af58-12d88dd01f21" = true
+
+# two rows
+"a6e5a2a2-fc9a-4b47-9f4f-ed9ad9fbe4bd" = true
+
+# three rows
+"97206a99-79ba-4b04-b1c5-3c0fa1e16925" = true
+
+# four rows
+"565a0431-c797-417c-a2c8-2935e01ce306" = true
+
+# five rows
+"06f9ea50-9f51-4eb2-b9a9-c00975686c27" = true
+
+# six rows
+"c3912965-ddb4-46a9-848e-3363e6b00b13" = true
+
+# ten rows
+"6cb26c66-7b57-4161-962c-81ec8c99f16b" = true
diff --git a/exercises/perfect-numbers/.meta/tests.toml b/exercises/perfect-numbers/.meta/tests.toml
new file mode 100644
index 000000000..af4b07b10
--- /dev/null
+++ b/exercises/perfect-numbers/.meta/tests.toml
@@ -0,0 +1,40 @@
+[canonical-tests]
+
+# Smallest perfect number is classified correctly
+"163e8e86-7bfd-4ee2-bd68-d083dc3381a3" = true
+
+# Medium perfect number is classified correctly
+"169a7854-0431-4ae0-9815-c3b6d967436d" = true
+
+# Large perfect number is classified correctly
+"ee3627c4-7b36-4245-ba7c-8727d585f402" = true
+
+# Smallest abundant number is classified correctly
+"80ef7cf8-9ea8-49b9-8b2d-d9cb3db3ed7e" = true
+
+# Medium abundant number is classified correctly
+"3e300e0d-1a12-4f11-8c48-d1027165ab60" = true
+
+# Large abundant number is classified correctly
+"ec7792e6-8786-449c-b005-ce6dd89a772b" = true
+
+# Smallest prime deficient number is classified correctly
+"e610fdc7-2b6e-43c3-a51c-b70fb37413ba" = true
+
+# Smallest non-prime deficient number is classified correctly
+"0beb7f66-753a-443f-8075-ad7fbd9018f3" = true
+
+# Medium deficient number is classified correctly
+"1c802e45-b4c6-4962-93d7-1cad245821ef" = true
+
+# Large deficient number is classified correctly
+"47dd569f-9e5a-4a11-9a47-a4e91c8c28aa" = true
+
+# Edge case (no factors other than itself) is classified correctly
+"a696dec8-6147-4d68-afad-d38de5476a56" = true
+
+# Zero is rejected (not a natural number)
+"72445cee-660c-4d75-8506-6c40089dc302" = true
+
+# Negative integer is rejected (not a natural number)
+"2d72ce2c-6802-49ac-8ece-c790ba3dae13" = true
diff --git a/exercises/phone-number/.meta/tests.toml b/exercises/phone-number/.meta/tests.toml
new file mode 100644
index 000000000..0c914d43d
--- /dev/null
+++ b/exercises/phone-number/.meta/tests.toml
@@ -0,0 +1,55 @@
+[canonical-tests]
+
+# cleans the number
+"79666dce-e0f1-46de-95a1-563802913c35" = true
+
+# cleans numbers with dots
+"c360451f-549f-43e4-8aba-fdf6cb0bf83f" = true
+
+# cleans numbers with multiple spaces
+"08f94c34-9a37-46a2-a123-2a8e9727395d" = true
+
+# invalid when 9 digits
+"598d8432-0659-4019-a78b-1c6a73691d21" = true
+
+# invalid when 11 digits does not start with a 1
+"57061c72-07b5-431f-9766-d97da7c4399d" = true
+
+# valid when 11 digits and starting with 1
+"9962cbf3-97bb-4118-ba9b-38ff49c64430" = true
+
+# valid when 11 digits and starting with 1 even with punctuation
+"fa724fbf-054c-4d91-95da-f65ab5b6dbca" = true
+
+# invalid when more than 11 digits
+"c6a5f007-895a-4fc5-90bc-a7e70f9b5cad" = true
+
+# invalid with letters
+"63f38f37-53f6-4a5f-bd86-e9b404f10a60" = true
+
+# invalid with punctuations
+"4bd97d90-52fd-45d3-b0db-06ab95b1244e" = true
+
+# invalid if area code starts with 0
+"d77d07f8-873c-4b17-8978-5f66139bf7d7" = true
+
+# invalid if area code starts with 1
+"c7485cfb-1e7b-4081-8e96-8cdb3b77f15e" = true
+
+# invalid if exchange code starts with 0
+"4d622293-6976-413d-b8bf-dd8a94d4e2ac" = true
+
+# invalid if exchange code starts with 1
+"4cef57b4-7d8e-43aa-8328-1e1b89001262" = true
+
+# invalid if area code starts with 0 on valid 11-digit number
+"9925b09c-1a0d-4960-a197-5d163cbe308c" = true
+
+# invalid if area code starts with 1 on valid 11-digit number
+"3f809d37-40f3-44b5-ad90-535838b1a816" = true
+
+# invalid if exchange code starts with 0 on valid 11-digit number
+"e08e5532-d621-40d4-b0cc-96c159276b65" = true
+
+# invalid if exchange code starts with 1 on valid 11-digit number
+"57b32f3d-696a-455c-8bf1-137b6d171cdf" = true
diff --git a/exercises/pig-latin/.meta/tests.toml b/exercises/pig-latin/.meta/tests.toml
new file mode 100644
index 000000000..b53c8851c
--- /dev/null
+++ b/exercises/pig-latin/.meta/tests.toml
@@ -0,0 +1,67 @@
+[canonical-tests]
+
+# word beginning with a
+"11567f84-e8c6-4918-aedb-435f0b73db57" = true
+
+# word beginning with e
+"f623f581-bc59-4f45-9032-90c3ca9d2d90" = true
+
+# word beginning with i
+"7dcb08b3-23a6-4e8a-b9aa-d4e859450d58" = true
+
+# word beginning with o
+"0e5c3bff-266d-41c8-909f-364e4d16e09c" = true
+
+# word beginning with u
+"614ba363-ca3c-4e96-ab09-c7320799723c" = true
+
+# word beginning with a vowel and followed by a qu
+"bf2538c6-69eb-4fa7-a494-5a3fec911326" = true
+
+# word beginning with p
+"e5be8a01-2d8a-45eb-abb4-3fcc9582a303" = true
+
+# word beginning with k
+"d36d1e13-a7ed-464d-a282-8820cb2261ce" = true
+
+# word beginning with x
+"d838b56f-0a89-4c90-b326-f16ff4e1dddc" = true
+
+# word beginning with q without a following u
+"bce94a7a-a94e-4e2b-80f4-b2bb02e40f71" = true
+
+# word beginning with ch
+"c01e049a-e3e2-451c-bf8e-e2abb7e438b8" = true
+
+# word beginning with qu
+"9ba1669e-c43f-4b93-837a-cfc731fd1425" = true
+
+# word beginning with qu and a preceding consonant
+"92e82277-d5e4-43d7-8dd3-3a3b316c41f7" = true
+
+# word beginning with th
+"79ae4248-3499-4d5b-af46-5cb05fa073ac" = true
+
+# word beginning with thr
+"e0b3ae65-f508-4de3-8999-19c2f8e243e1" = true
+
+# word beginning with sch
+"20bc19f9-5a35-4341-9d69-1627d6ee6b43" = true
+
+# word beginning with yt
+"54b796cb-613d-4509-8c82-8fbf8fc0af9e" = true
+
+# word beginning with xr
+"8c37c5e1-872e-4630-ba6e-d20a959b67f6" = true
+
+# y is treated like a consonant at the beginning of a word
+"a4a36d33-96f3-422c-a233-d4021460ff00" = true
+
+# y is treated like a vowel at the end of a consonant cluster
+"adc90017-1a12-4100-b595-e346105042c7" = true
+
+# y as second letter in two letter word
+"29b4ca3d-efe5-4a95-9a54-8467f2e5e59a" = true
+
+# a whole phrase
+"44616581-5ce3-4a81-82d0-40c7ab13d2cf" = true
diff --git a/exercises/poker/.meta/tests.toml b/exercises/poker/.meta/tests.toml
new file mode 100644
index 000000000..b5c223c87
--- /dev/null
+++ b/exercises/poker/.meta/tests.toml
@@ -0,0 +1,85 @@
+[canonical-tests]
+
+# single hand always wins
+"161f485e-39c2-4012-84cf-bec0c755b66c" = true
+
+# highest card out of all hands wins
+"370ac23a-a00f-48a9-9965-6f3fb595cf45" = true
+
+# a tie has multiple winners
+"d94ad5a7-17df-484b-9932-c64fc26cff52" = true
+
+# multiple hands with the same high cards, tie compares next highest ranked, down to last card
+"61ed83a9-cfaa-40a5-942a-51f52f0a8725" = true
+
+# one pair beats high card
+"f7175a89-34ff-44de-b3d7-f6fd97d1fca4" = true
+
+# highest pair wins
+"e114fd41-a301-4111-a9e7-5a7f72a76561" = true
+
+# two pairs beats one pair
+"935bb4dc-a622-4400-97fa-86e7d06b1f76" = true
+
+# both hands have two pairs, highest ranked pair wins
+"c8aeafe1-6e3d-4711-a6de-5161deca91fd" = true
+
+# both hands have two pairs, with the same highest ranked pair, tie goes to low pair
+"88abe1ba-7ad7-40f3-847e-0a26f8e46a60" = true
+
+# both hands have two identically ranked pairs, tie goes to remaining card (kicker)
+"15a7a315-0577-47a3-9981-d6cf8e6f387b" = true
+
+# three of a kind beats two pair
+"21e9f1e6-2d72-49a1-a930-228e5e0195dc" = true
+
+# both hands have three of a kind, tie goes to highest ranked triplet
+"c2fffd1f-c287-480f-bf2d-9628e63bbcc3" = true
+
+# with multiple decks, two players can have same three of a kind, ties go to highest remaining cards
+"eb856cc2-481c-4b0d-9835-4d75d07a5d9d" = true
+
+# a straight beats three of a kind
+"a858c5d9-2f28-48e7-9980-b7fa04060a60" = true
+
+# aces can end a straight (10 J Q K A)
+"73c9c756-e63e-4b01-a88d-0d4491a7a0e3" = true
+
+# aces can start a straight (A 2 3 4 5)
+"76856b0d-35cd-49ce-a492-fe5db53abc02" = true
+
+# both hands with a straight, tie goes to highest ranked card
+"6980c612-bbff-4914-b17a-b044e4e69ea1" = true
+
+# even though an ace is usually high, a 5-high straight is the lowest-scoring straight
+"5135675c-c2fc-4e21-9ba3-af77a32e9ba4" = true
+
+# flush beats a straight
+"c601b5e6-e1df-4ade-b444-b60ce13b2571" = true
+
+# both hands have a flush, tie goes to high card, down to the last one if necessary
+"4d90261d-251c-49bd-a468-896bf10133de" = true
+
+# full house beats a flush
+"3a19361d-8974-455c-82e5-f7152f5dba7c" = true
+
+# both hands have a full house, tie goes to highest-ranked triplet
+"eb73d0e6-b66c-4f0f-b8ba-bf96bc0a67f0" = true
+
+# with multiple decks, both hands have a full house with the same triplet, tie goes to the pair
+"34b51168-1e43-4c0d-9b32-e356159b4d5d" = true
+
+# four of a kind beats a full house
+"d61e9e99-883b-4f99-b021-18f0ae50c5f4" = true
+
+# both hands have four of a kind, tie goes to high quad
+"2e1c8c63-e0cb-4214-a01b-91954490d2fe" = true
+
+# with multiple decks, both hands with identical four of a kind, tie determined by kicker
+"892ca75d-5474-495d-9f64-a6ce2dcdb7e1" = true
+
+# straight flush beats four of a kind
+"923bd910-dc7b-4f7d-a330-8b42ec10a3ac" = true
+
+# both hands have straight flush, tie goes to highest-ranked card
+"d0927f70-5aec-43db-aed8-1cbd1b6ee9ad" = true
diff --git a/exercises/pov/.meta/tests.toml b/exercises/pov/.meta/tests.toml
new file mode 100644
index 000000000..5995f3f54
--- /dev/null
+++ b/exercises/pov/.meta/tests.toml
@@ -0,0 +1,46 @@
+[canonical-tests]
+
+# Results in the same tree if the input tree is a singleton
+"1b3cd134-49ad-4a7d-8376-7087b7e70792" = true
+
+# Can reroot a tree with a parent and one sibling
+"0778c745-0636-40de-9edd-25a8f40426f6" = true
+
+# Can reroot a tree with a parent and many siblings
+"fdfdef0a-4472-4248-8bcf-19cf33f9c06e" = true
+
+# Can reroot a tree with new root deeply nested in tree
+"cbcf52db-8667-43d8-a766-5d80cb41b4bb" = true
+
+# Moves children of the new root to same level as former parent
+"e27fa4fa-648d-44cd-90af-d64a13d95e06" = true
+
+# Can reroot a complex tree with cousins
+"09236c7f-7c83-42cc-87a1-25afa60454a3" = true
+
+# Errors if target does not exist in a singleton tree
+"f41d5eeb-8973-448f-a3b0-cc1e019a4193" = true
+
+# Errors if target does not exist in a large tree
+"9dc0a8b3-df02-4267-9a41-693b6aff75e7" = true
+
+# Can find path to parent
+"02d1f1d9-428d-4395-b026-2db35ffa8f0a" = true
+
+# Can find path to sibling
+"d0002674-fcfb-4cdc-9efa-bfc54e3c31b5" = true
+
+# Can find path to cousin
+"c9877cd1-0a69-40d4-b362-725763a5c38f" = true
+
+# Can find path not involving root
+"9fb17a82-2c14-4261-baa3-2f3f234ffa03" = true
+
+# Can find path from nodes other than x
+"5124ed49-7845-46ad-bc32-97d5ac7451b2" = true
+
+# Errors if destination does not exist
+"f52a183c-25cc-4c87-9fc9-0e7f81a5725c" = true
+
+# Errors if source does not exist
+"f4fe18b9-b4a2-4bd5-a694-e179155c2149" = true
diff --git a/exercises/prime-factors/.meta/tests.toml b/exercises/prime-factors/.meta/tests.toml
new file mode 100644
index 000000000..ba44aa8fa
--- /dev/null
+++ b/exercises/prime-factors/.meta/tests.toml
@@ -0,0 +1,22 @@
+[canonical-tests]
+
+# no factors
+"924fc966-a8f5-4288-82f2-6b9224819ccd" = true
+
+# prime number
+"17e30670-b105-4305-af53-ddde182cb6ad" = true
+
+# square of a prime
+"f59b8350-a180-495a-8fb1-1712fbee1158" = true
+
+# cube of a prime
+"bc8c113f-9580-4516-8669-c5fc29512ceb" = true
+
+# product of primes and non-primes
+"00485cd3-a3fe-4fbe-a64a-a4308fc1f870" = true
+
+# product of primes
+"02251d54-3ca1-4a9b-85e1-b38f4b0ccb91" = true
+
+# factors include a large prime
+"070cf8dc-e202-4285-aa37-8d775c9cd473" = true
diff --git a/exercises/protein-translation/.meta/tests.toml b/exercises/protein-translation/.meta/tests.toml
new file mode 100644
index 000000000..b626eb630
--- /dev/null
+++ b/exercises/protein-translation/.meta/tests.toml
@@ -0,0 +1,70 @@
+[canonical-tests]
+
+# Methionine RNA sequence
+"96d3d44f-34a2-4db4-84cd-fff523e069be" = true
+
+# Phenylalanine RNA sequence 1
+"1b4c56d8-d69f-44eb-be0e-7b17546143d9" = true
+
+# Phenylalanine RNA sequence 2
+"81b53646-bd57-4732-b2cb-6b1880e36d11" = true
+
+# Leucine RNA sequence 1
+"42f69d4f-19d2-4d2c-a8b0-f0ae9ee1b6b4" = true
+
+# Leucine RNA sequence 2
+"ac5edadd-08ed-40a3-b2b9-d82bb50424c4" = true
+
+# Serine RNA sequence 1
+"8bc36e22-f984-44c3-9f6b-ee5d4e73f120" = true
+
+# Serine RNA sequence 2
+"5c3fa5da-4268-44e5-9f4b-f016ccf90131" = true
+
+# Serine RNA sequence 3
+"00579891-b594-42b4-96dc-7ff8bf519606" = true
+
+# Serine RNA sequence 4
+"08c61c3b-fa34-4950-8c4a-133945570ef6" = true
+
+# Tyrosine RNA sequence 1
+"54e1e7d8-63c0-456d-91d2-062c72f8eef5" = true
+
+# Tyrosine RNA sequence 2
+"47bcfba2-9d72-46ad-bbce-22f7666b7eb1" = true
+
+# Cysteine RNA sequence 1
+"3a691829-fe72-43a7-8c8e-1bd083163f72" = true
+
+# Cysteine RNA sequence 2
+"1b6f8a26-ca2f-43b8-8262-3ee446021767" = true
+
+# Tryptophan RNA sequence
+"1e91c1eb-02c0-48a0-9e35-168ad0cb5f39" = true
+
+# STOP codon RNA sequence 1
+"e547af0b-aeab-49c7-9f13-801773a73557" = true
+
+# STOP codon RNA sequence 2
+"67640947-ff02-4f23-a2ef-816f8a2ba72e" = true
+
+# STOP codon RNA sequence 3
+"9c2ad527-ebc9-4ace-808b-2b6447cb54cb" = true
+
+# Translate RNA strand into correct protein list
+"d0f295df-fb70-425c-946c-ec2ec185388e" = true
+
+# Translation stops if STOP codon at beginning of sequence
+"e30e8505-97ec-4e5f-a73e-5726a1faa1f4" = true
+
+# Translation stops if STOP codon at end of two-codon sequence
+"5358a20b-6f4c-4893-bce4-f929001710f3" = true
+
+# Translation stops if STOP codon at end of three-codon sequence
+"ba16703a-1a55-482f-bb07-b21eef5093a3" = true
+
+# Translation stops if STOP codon in middle of three-codon sequence
+"4089bb5a-d5b4-4e71-b79e-b8d1f14a2911" = true
+
+# Translation stops if STOP codon in middle of six-codon sequence
+"2c2a2a60-401f-4a80-b977-e0715b23b93d" = true
diff --git a/exercises/proverb/.meta/tests.toml b/exercises/proverb/.meta/tests.toml
new file mode 100644
index 000000000..efbcc4d38
--- /dev/null
+++ b/exercises/proverb/.meta/tests.toml
@@ -0,0 +1,19 @@
+[canonical-tests]
+
+# zero pieces
+"e974b73e-7851-484f-8d6d-92e07fe742fc" = true
+
+# one piece
+"2fcd5f5e-8b82-4e74-b51d-df28a5e0faa4" = true
+
+# two pieces
+"d9d0a8a1-d933-46e2-aa94-eecf679f4b0e" = true
+
+# three pieces
+"c95ef757-5e94-4f0d-a6cb-d2083f5e5a83" = true
+
+# full proverb
+"433fb91c-35a2-4d41-aeab-4de1e82b2126" = true
+
+# four pieces modernized
+"c1eefa5a-e8d9-41c7-91d4-99fab6d6b9f7" = true
diff --git a/exercises/pythagorean-triplet/.meta/tests.toml b/exercises/pythagorean-triplet/.meta/tests.toml
new file mode 100644
index 000000000..544d6a142
--- /dev/null
+++ b/exercises/pythagorean-triplet/.meta/tests.toml
@@ -0,0 +1,22 @@
+[canonical-tests]
+
+# triplets whose sum is 12
+"a19de65d-35b8-4480-b1af-371d9541e706" = true
+
+# triplets whose sum is 108
+"48b21332-0a3d-43b2-9a52-90b2a6e5c9f5" = true
+
+# triplets whose sum is 1000
+"dffc1266-418e-4daa-81af-54c3e95c3bb5" = true
+
+# no matching triplets for 1001
+"5f86a2d4-6383-4cce-93a5-e4489e79b186" = true
+
+# returns all matching triplets
+"bf17ba80-1596-409a-bb13-343bdb3b2904" = true
+
+# several matching triplets
+"9d8fb5d5-6c6f-42df-9f95-d3165963ac57" = true
+
+# triplets for large number
+"f5be5734-8aa0-4bd1-99a2-02adcc4402b4" = true
diff --git a/exercises/queen-attack/.meta/tests.toml b/exercises/queen-attack/.meta/tests.toml
new file mode 100644
index 000000000..5ce2b93fc
--- /dev/null
+++ b/exercises/queen-attack/.meta/tests.toml
@@ -0,0 +1,40 @@
+[canonical-tests]
+
+# queen with a valid position
+"3ac4f735-d36c-44c4-a3e2-316f79704203" = true
+
+# queen must have positive row
+"4e812d5d-b974-4e38-9a6b-8e0492bfa7be" = true
+
+# queen must have row on board
+"f07b7536-b66b-4f08-beb9-4d70d891d5c8" = true
+
+# queen must have positive column
+"15a10794-36d9-4907-ae6b-e5a0d4c54ebe" = true
+
+# queen must have column on board
+"6907762d-0e8a-4c38-87fb-12f2f65f0ce4" = true
+
+# can not attack
+"33ae4113-d237-42ee-bac1-e1e699c0c007" = true
+
+# can attack on same row
+"eaa65540-ea7c-4152-8c21-003c7a68c914" = true
+
+# can attack on same column
+"bae6f609-2c0e-4154-af71-af82b7c31cea" = true
+
+# can attack on first diagonal
+"0e1b4139-b90d-4562-bd58-dfa04f1746c7" = true
+
+# can attack on second diagonal
+"ff9b7ed4-e4b6-401b-8d16-bc894d6d3dcd" = true
+
+# can attack on third diagonal
+"0a71e605-6e28-4cc2-aa47-d20a2e71037a" = true
+
+# can attack on fourth diagonal
+# Haskell intentionally uses the old values for this test
+# upstream change:
+# https://github.com/exercism/problem-specifications/pull/1504
+"0790b588-ae73-4f1f-a968-dd0b34f45f86" = true
diff --git a/exercises/rail-fence-cipher/.meta/tests.toml b/exercises/rail-fence-cipher/.meta/tests.toml
new file mode 100644
index 000000000..6e1a9ab11
--- /dev/null
+++ b/exercises/rail-fence-cipher/.meta/tests.toml
@@ -0,0 +1,19 @@
+[canonical-tests]
+
+# encode with two rails
+"46dc5c50-5538-401d-93a5-41102680d068" = true
+
+# encode with three rails
+"25691697-fbd8-4278-8c38-b84068b7bc29" = true
+
+# encode with ending in the middle
+"384f0fea-1442-4f1a-a7c4-5cbc2044002c" = true
+
+# decode with three rails
+"cd525b17-ec34-45ef-8f0e-4f27c24a7127" = true
+
+# decode with five rails
+"dd7b4a98-1a52-4e5c-9499-cbb117833507" = true
+
+# decode with six rails
+"93e1ecf4-fac9-45d9-9cd2-591f47d3b8d3" = true
diff --git a/exercises/raindrops/.meta/tests.toml b/exercises/raindrops/.meta/tests.toml
new file mode 100644
index 000000000..ececbef90
--- /dev/null
+++ b/exercises/raindrops/.meta/tests.toml
@@ -0,0 +1,55 @@
+[canonical-tests]
+
+# the sound for 1 is 1
+"1575d549-e502-46d4-a8e1-6b7bec6123d8" = true
+
+# the sound for 3 is Pling
+"1f51a9f9-4895-4539-b182-d7b0a5ab2913" = true
+
+# the sound for 5 is Plang
+"2d9bfae5-2b21-4bcd-9629-c8c0e388f3e0" = true
+
+# the sound for 7 is Plong
+"d7e60daa-32ef-4c23-b688-2abff46c4806" = true
+
+# the sound for 6 is Pling as it has a factor 3
+"6bb4947b-a724-430c-923f-f0dc3d62e56a" = true
+
+# 2 to the power 3 does not make a raindrop sound as 3 is the exponent not the base
+"ce51e0e8-d9d4-446d-9949-96eac4458c2d" = true
+
+# the sound for 9 is Pling as it has a factor 3
+"0dd66175-e3e2-47fc-8750-d01739856671" = true
+
+# the sound for 10 is Plang as it has a factor 5
+"022c44d3-2182-4471-95d7-c575af225c96" = true
+
+# the sound for 14 is Plong as it has a factor of 7
+"37ab74db-fed3-40ff-b7b9-04acdfea8edf" = true
+
+# the sound for 15 is PlingPlang as it has factors 3 and 5
+"31f92999-6afb-40ee-9aa4-6d15e3334d0f" = true
+
+# the sound for 21 is PlingPlong as it has factors 3 and 7
+"ff9bb95d-6361-4602-be2c-653fe5239b54" = true
+
+# the sound for 25 is Plang as it has a factor 5
+"d2e75317-b72e-40ab-8a64-6734a21dece1" = true
+
+# the sound for 27 is Pling as it has a factor 3
+"a09c4c58-c662-4e32-97fe-f1501ef7125c" = true
+
+# the sound for 35 is PlangPlong as it has factors 5 and 7
+"bdf061de-8564-4899-a843-14b48b722789" = true
+
+# the sound for 49 is Plong as it has a factor 7
+"c4680bee-69ba-439d-99b5-70c5fd1a7a83" = true
+
+# the sound for 52 is 52
+"17f2bc9a-b65a-4d23-8ccd-266e8c271444" = true
+
+# the sound for 105 is PlingPlangPlong as it has factors 3, 5 and 7
+"e46677ed-ff1a-419f-a740-5c713d2830e4" = true
+
+# the sound for 3125 is Plang as it has a factor 5
+"13c6837a-0fcd-4b86-a0eb-20572f7deb0b" = true
diff --git a/exercises/resistor-color-duo/.meta/tests.toml b/exercises/resistor-color-duo/.meta/tests.toml
new file mode 100644
index 000000000..5e78d911b
--- /dev/null
+++ b/exercises/resistor-color-duo/.meta/tests.toml
@@ -0,0 +1,16 @@
+[canonical-tests]
+
+# Brown and black
+"ce11995a-5b93-4950-a5e9-93423693b2fc" = true
+
+# Blue and grey
+"7bf82f7a-af23-48ba-a97d-38d59406a920" = true
+
+# Yellow and violet
+"f1886361-fdfd-4693-acf8-46726fe24e0c" = true
+
+# Orange and orange
+"77a8293d-2a83-4016-b1af-991acc12b9fe" = true
+
+# Ignore additional colors
+"0c4fb44f-db7c-4d03-afa8-054350f156a8" = true
diff --git a/exercises/resistor-color-trio/.meta/tests.toml b/exercises/resistor-color-trio/.meta/tests.toml
new file mode 100644
index 000000000..ef40719d9
--- /dev/null
+++ b/exercises/resistor-color-trio/.meta/tests.toml
@@ -0,0 +1,16 @@
+[canonical-tests]
+
+# Orange and orange and black
+"d6863355-15b7-40bb-abe0-bfb1a25512ed" = true
+
+# Blue and grey and brown
+"1224a3a9-8c8e-4032-843a-5224e04647d6" = true
+
+# Red and black and red
+"b8bda7dc-6b95-4539-abb2-2ad51d66a207" = true
+
+# Green and brown and orange
+"5b1e74bc-d838-4eda-bbb3-eaba988e733b" = true
+
+# Yellow and violet and yellow
+"f5d37ef9-1919-4719-a90d-a33c5a6934c9" = true
diff --git a/exercises/rna-transcription/.meta/tests.toml b/exercises/rna-transcription/.meta/tests.toml
new file mode 100644
index 000000000..d97f860af
--- /dev/null
+++ b/exercises/rna-transcription/.meta/tests.toml
@@ -0,0 +1,19 @@
+[canonical-tests]
+
+# Empty RNA sequence
+"b4631f82-c98c-4a2f-90b3-c5c2b6c6f661" = true
+
+# RNA complement of cytosine is guanine
+"a9558a3c-318c-4240-9256-5d5ed47005a6" = true
+
+# RNA complement of guanine is cytosine
+"6eedbb5c-12cb-4c8b-9f51-f8320b4dc2e7" = true
+
+# RNA complement of thymine is adenine
+"870bd3ec-8487-471d-8d9a-a25046488d3e" = true
+
+# RNA complement of adenine is uracil
+"aade8964-02e1-4073-872f-42d3ffd74c5f" = true
+
+# RNA complement
+"79ed2757-f018-4f47-a1d7-34a559392dbf" = true
diff --git a/exercises/robot-simulator/.meta/tests.toml b/exercises/robot-simulator/.meta/tests.toml
new file mode 100644
index 000000000..7244c7457
--- /dev/null
+++ b/exercises/robot-simulator/.meta/tests.toml
@@ -0,0 +1,55 @@
+[canonical-tests]
+
+# at origin facing north
+"c557c16d-26c1-4e06-827c-f6602cd0785c" = true
+
+# at negative position facing south
+"bf0dffce-f11c-4cdb-8a5e-2c89d8a5a67d" = true
+
+# changes north to east
+"8cbd0086-6392-4680-b9b9-73cf491e67e5" = true
+
+# changes east to south
+"8abc87fc-eab2-4276-93b7-9c009e866ba1" = true
+
+# changes south to west
+"3cfe1b85-bbf2-4bae-b54d-d73e7e93617a" = true
+
+# changes west to north
+"5ea9fb99-3f2c-47bd-86f7-46b7d8c3c716" = true
+
+# changes north to west
+"fa0c40f5-6ba3-443d-a4b3-58cbd6cb8d63" = true
+
+# changes west to south
+"da33d734-831f-445c-9907-d66d7d2a92e2" = true
+
+# changes south to east
+"bd1ca4b9-4548-45f4-b32e-900fc7c19389" = true
+
+# changes east to north
+"2de27b67-a25c-4b59-9883-bc03b1b55bba" = true
+
+# facing north increments Y
+"f0dc2388-cddc-4f83-9bed-bcf46b8fc7b8" = true
+
+# facing south decrements Y
+"2786cf80-5bbf-44b0-9503-a89a9c5789da" = true
+
+# facing east increments X
+"84bf3c8c-241f-434d-883d-69817dbd6a48" = true
+
+# facing west decrements X
+"bb69c4a7-3bbf-4f64-b415-666fa72d7b04" = true
+
+# moving east and north from README
+"e34ac672-4ed4-4be3-a0b8-d9af259cbaa1" = true
+
+# moving west and north
+"f30e4955-4b47-4aa3-8b39-ae98cfbd515b" = true
+
+# moving west and south
+"3e466bf6-20ab-4d79-8b51-264165182fca" = true
+
+# moving east and north
+"41f0bb96-c617-4e6b-acff-a4b279d44514" = true
diff --git a/exercises/roman-numerals/.meta/tests.toml b/exercises/roman-numerals/.meta/tests.toml
new file mode 100644
index 000000000..42767c90a
--- /dev/null
+++ b/exercises/roman-numerals/.meta/tests.toml
@@ -0,0 +1,58 @@
+[canonical-tests]
+
+# 1 is a single I
+"19828a3a-fbf7-4661-8ddd-cbaeee0e2178" = true
+
+# 2 is two I's
+"f088f064-2d35-4476-9a41-f576da3f7b03" = true
+
+# 3 is three I's
+"b374a79c-3bea-43e6-8db8-1286f79c7106" = true
+
+# 4, being 5 - 1, is IV
+"05a0a1d4-a140-4db1-82e8-fcc21fdb49bb" = true
+
+# 5 is a single V
+"57c0f9ad-5024-46ab-975d-de18c430b290" = true
+
+# 6, being 5 + 1, is VI
+"20a2b47f-e57f-4797-a541-0b3825d7f249" = true
+
+# 9, being 10 - 1, is IX
+"ff3fb08c-4917-4aab-9f4e-d663491d083d" = true
+
+# 20 is two X's
+"2bda64ca-7d28-4c56-b08d-16ce65716cf6" = true
+
+# 48 is not 50 - 2 but rather 40 + 8
+"a1f812ef-84da-4e02-b4f0-89c907d0962c" = true
+
+# 49 is not 40 + 5 + 4 but rather 50 - 10 + 10 - 1
+"607ead62-23d6-4c11-a396-ef821e2e5f75" = true
+
+# 50 is a single L
+"d5b283d4-455d-4e68-aacf-add6c4b51915" = true
+
+# 90, being 100 - 10, is XC
+"46b46e5b-24da-4180-bfe2-2ef30b39d0d0" = true
+
+# 100 is a single C
+"30494be1-9afb-4f84-9d71-db9df18b55e3" = true
+
+# 60, being 50 + 10, is LX
+"267f0207-3c55-459a-b81d-67cec7a46ed9" = true
+
+# 400, being 500 - 100, is CD
+"cdb06885-4485-4d71-8bfb-c9d0f496b404" = true
+
+# 500 is a single D
+"6b71841d-13b2-46b4-ba97-dec28133ea80" = true
+
+# 900, being 1000 - 100, is CM
+"432de891-7fd6-4748-a7f6-156082eeca2f" = true
+
+# 1000 is a single M
+"e6de6d24-f668-41c0-88d7-889c0254d173" = true
+
+# 3000 is three M's
+"bb550038-d4eb-4be2-a9ce-f21961ac3bc6" = true
diff --git a/exercises/rotational-cipher/.meta/tests.toml b/exercises/rotational-cipher/.meta/tests.toml
new file mode 100644
index 000000000..446abff5b
--- /dev/null
+++ b/exercises/rotational-cipher/.meta/tests.toml
@@ -0,0 +1,31 @@
+[canonical-tests]
+
+# rotate a by 0, same output as input
+"74e58a38-e484-43f1-9466-877a7515e10f" = true
+
+# rotate a by 1
+"7ee352c6-e6b0-4930-b903-d09943ecb8f5" = true
+
+# rotate a by 26, same output as input
+"edf0a733-4231-4594-a5ee-46a4009ad764" = true
+
+# rotate m by 13
+"e3e82cb9-2a5b-403f-9931-e43213879300" = true
+
+# rotate n by 13 with wrap around alphabet
+"19f9eb78-e2ad-4da4-8fe3-9291d47c1709" = true
+
+# rotate capital letters
+"a116aef4-225b-4da9-884f-e8023ca6408a" = true
+
+# rotate spaces
+"71b541bb-819c-4dc6-a9c3-132ef9bb737b" = true
+
+# rotate numbers
+"ef32601d-e9ef-4b29-b2b5-8971392282e6" = true
+
+# rotate punctuation
+"32dd74f6-db2b-41a6-b02c-82eb4f93e549" = true
+
+# rotate all letters
+"9fb93fe6-42b0-46e6-9ec1-0bf0a062d8c9" = true
diff --git a/exercises/run-length-encoding/.meta/tests.toml b/exercises/run-length-encoding/.meta/tests.toml
new file mode 100644
index 000000000..f06c8d6d1
--- /dev/null
+++ b/exercises/run-length-encoding/.meta/tests.toml
@@ -0,0 +1,40 @@
+[canonical-tests]
+
+# empty string
+"ad53b61b-6ffc-422f-81a6-61f7df92a231" = true
+
+# single characters only are encoded without count
+"52012823-b7e6-4277-893c-5b96d42f82de" = true
+
+# string with no single characters
+"b7868492-7e3a-415f-8da3-d88f51f80409" = true
+
+# single characters mixed with repeated characters
+"859b822b-6e9f-44d6-9c46-6091ee6ae358" = true
+
+# multiple whitespace mixed in string
+"1b34de62-e152-47be-bc88-469746df63b3" = true
+
+# lowercase characters
+"abf176e2-3fbd-40ad-bb2f-2dd6d4df721a" = true
+
+# empty string
+"7ec5c390-f03c-4acf-ac29-5f65861cdeb5" = true
+
+# single characters only
+"ad23f455-1ac2-4b0e-87d0-b85b10696098" = true
+
+# string with no single characters
+"21e37583-5a20-4a0e-826c-3dee2c375f54" = true
+
+# single characters with repeated characters
+"1389ad09-c3a8-4813-9324-99363fba429c" = true
+
+# multiple whitespace mixed in string
+"3f8e3c51-6aca-4670-b86c-a213bf4706b0" = true
+
+# lower case string
+"29f721de-9aad-435f-ba37-7662df4fb551" = true
+
+# encode followed by decode gives original string
+"2a762efd-8695-4e04-b0d6-9736899fbc16" = true
diff --git a/exercises/saddle-points/.meta/tests.toml b/exercises/saddle-points/.meta/tests.toml
new file mode 100644
index 000000000..edff0d8fe
--- /dev/null
+++ b/exercises/saddle-points/.meta/tests.toml
@@ -0,0 +1,28 @@
+[canonical-tests]
+
+# Can identify single saddle point
+"3e374e63-a2e0-4530-a39a-d53c560382bd" = true
+
+# Can identify that empty matrix has no saddle points
+"6b501e2b-6c1f-491f-b1bb-7f278f760534" = true
+
+# Can identify lack of saddle points when there are none
+"8c27cc64-e573-4fcb-a099-f0ae863fb02f" = true
+
+# Can identify multiple saddle points in a column
+"6d1399bd-e105-40fd-a2c9-c6609507d7a3" = true
+
+# Can identify multiple saddle points in a row
+"3e81dce9-53b3-44e6-bf26-e328885fd5d1" = true
+
+# Can identify saddle point in bottom right corner
+"88868621-b6f4-4837-bb8b-3fad8b25d46b" = true
+
+# Can identify saddle points in a non square matrix
+"5b9499ca-fcea-4195-830a-9c4584a0ee79" = true
+
+# Can identify that saddle points in a single column matrix are those with the minimum value
+"ee99ccd2-a1f1-4283-ad39-f8c70f0cf594" = true
+
+# Can identify that saddle points in a single row matrix are those with the maximum value
+"63abf709-a84b-407f-a1b3-456638689713" = true
diff --git a/exercises/say/.meta/tests.toml b/exercises/say/.meta/tests.toml
new file mode 100644
index 000000000..99c8473fa
--- /dev/null
+++ b/exercises/say/.meta/tests.toml
@@ -0,0 +1,46 @@
+[canonical-tests]
+
+# zero
+"5d22a120-ba0c-428c-bd25-8682235d83e8" = true
+
+# one
+"9b5eed77-dbf6-439d-b920-3f7eb58928f6" = true
+
+# fourteen
+"7c499be1-612e-4096-a5e1-43b2f719406d" = true
+
+# twenty
+"f541dd8e-f070-4329-92b4-b7ce2fcf06b4" = true
+
+# twenty-two
+"d78601eb-4a84-4bfa-bf0e-665aeb8abe94" = true
+
+# one hundred
+"e417d452-129e-4056-bd5b-6eb1df334dce" = true
+
+# one hundred twenty-three
+"d6924f30-80ba-4597-acf6-ea3f16269da8" = true
+
+# one thousand
+"3d83da89-a372-46d3-b10d-de0c792432b3" = true
+
+# one thousand two hundred thirty-four
+"865af898-1d5b-495f-8ff0-2f06d3c73709" = true
+
+# one million
+"b6a3f442-266e-47a3-835d-7f8a35f6cf7f" = true
+
+# one million two thousand three hundred forty-five
+"2cea9303-e77e-4212-b8ff-c39f1978fc70" = true
+
+# one billion
+"3e240eeb-f564-4b80-9421-db123f66a38f" = true
+
+# a big number
+"9a43fed1-c875-4710-8286-5065d73b8a9e" = true
+
+# numbers below zero are out of range
+"49a6a17b-084e-423e-994d-a87c0ecc05ef" = true
+
+# numbers above 999,999,999,999 are out of range
+"4d6492eb-5853-4d16-9d34-b0f61b261fd9" = true
diff --git a/exercises/scrabble-score/.meta/tests.toml b/exercises/scrabble-score/.meta/tests.toml
new file mode 100644
index 000000000..5357235b4
--- /dev/null
+++ b/exercises/scrabble-score/.meta/tests.toml
@@ -0,0 +1,34 @@
+[canonical-tests]
+
+# lowercase letter
+"f46cda29-1ca5-4ef2-bd45-388a767e3db2" = true
+
+# uppercase letter
+"f7794b49-f13e-45d1-a933-4e48459b2201" = true
+
+# valuable letter
+"eaba9c76-f9fa-49c9-a1b0-d1ba3a5b31fa" = true
+
+# short word
+"f3c8c94e-bb48-4da2-b09f-e832e103151e" = true
+
+# short, valuable word
+"71e3d8fa-900d-4548-930e-68e7067c4615" = true
+
+# medium word
+"d3088ad9-570c-4b51-8764-c75d5a430e99" = true
+
+# medium, valuable word
+"fa20c572-ad86-400a-8511-64512daac352" = true
+
+# long, mixed-case word
+"9336f0ba-9c2b-4fa0-bd1c-2e2d328cf967" = true
+
+# english-like word
+"1e34e2c3-e444-4ea7-b598-3c2b46fd2c10" = true
+
+# empty input
+"4efe3169-b3b6-4334-8bae-ff4ef24a7e4f" = true
+
+# entire alphabet available
+"3b305c1c-f260-4e15-a5b5-cb7d3ea7c3d7" = true
diff --git a/exercises/secret-handshake/.meta/tests.toml b/exercises/secret-handshake/.meta/tests.toml
new file mode 100644
index 000000000..7f445862f
--- /dev/null
+++ b/exercises/secret-handshake/.meta/tests.toml
@@ -0,0 +1,34 @@
+[canonical-tests]
+
+# wink for 1
+"b8496fbd-6778-468c-8054-648d03c4bb23" = true
+
+# double blink for 10
+"83ec6c58-81a9-4fd1-bfaf-0160514fc0e3" = true
+
+# close your eyes for 100
+"0e20e466-3519-4134-8082-5639d85fef71" = true
+
+# jump for 1000
+"b339ddbb-88b7-4b7d-9b19-4134030d9ac0" = true
+
+# combine two actions
+"40499fb4-e60c-43d7-8b98-0de3ca44e0eb" = true
+
+# reverse two actions
+"9730cdd5-ef27-494b-afd3-5c91ad6c3d9d" = true
+
+# reversing one action gives the same action
+"0b828205-51ca-45cd-90d5-f2506013f25f" = true
+
+# reversing no actions still gives no actions
+"9949e2ac-6c9c-4330-b685-2089ab28b05f" = true
+
+# all possible actions
+"23fdca98-676b-4848-970d-cfed7be39f81" = true
+
+# reverse all possible actions
+"ae8fe006-d910-4d6f-be00-54b7c3799e79" = true
+
+# do nothing for zero
+"3d36da37-b31f-4cdb-a396-d93a2ee1c4a5" = true
diff --git a/exercises/series/.meta/tests.toml b/exercises/series/.meta/tests.toml
new file mode 100644
index 000000000..3e05ec7e9
--- /dev/null
+++ b/exercises/series/.meta/tests.toml
@@ -0,0 +1,31 @@
+[canonical-tests]
+
+# slices of one from one
+"7ae7a46a-d992-4c2a-9c15-a112d125ebad" = true
+
+# slices of one from two
+"3143b71d-f6a5-4221-aeae-619f906244d2" = true
+
+# slices of two
+"dbb68ff5-76c5-4ccd-895a-93dbec6d5805" = true
+
+# slices of two overlap
+"19bbea47-c987-4e11-a7d1-e103442adf86" = true
+
+# slices can include duplicates
+"8e17148d-ba0a-4007-a07f-d7f87015d84c" = true
+
+# slices of a long series
+"bd5b085e-f612-4f81-97a8-6314258278b0" = true
+
+# slice length is too large
+"6d235d85-46cf-4fae-9955-14b6efef27cd" = true
+
+# slice length cannot be zero
+"d34004ad-8765-4c09-8ba1-ada8ce776806" = true
+
+# slice length cannot be negative
+"10ab822d-8410-470a-a85d-23fbeb549e54" = true
+
+# empty series is invalid
+"c7ed0812-0e4b-4bf3-99c4-28cbbfc246a2" = true
diff --git a/exercises/sgf-parsing/.meta/tests.toml b/exercises/sgf-parsing/.meta/tests.toml
new file mode 100644
index 000000000..41098717c
--- /dev/null
+++ b/exercises/sgf-parsing/.meta/tests.toml
@@ -0,0 +1,40 @@
+[canonical-tests]
+
+# empty input
+"2668d5dc-109f-4f71-b9d5-8d06b1d6f1cd" = true
+
+# tree with no nodes
+"84ded10a-94df-4a30-9457-b50ccbdca813" = true
+
+# node without tree
+"0a6311b2-c615-4fa7-800e-1b1cbb68833d" = true
+
+# node without properties
+"8c419ed8-28c4-49f6-8f2d-433e706110ef" = true
+
+# single node tree
+"8209645f-32da-48fe-8e8f-b9b562c26b49" = true
+
+# multiple properties
+"6c995856-b919-4c75-8fd6-c2c3c31b37dc" = true
+
+# properties without delimiter
+"a771f518-ec96-48ca-83c7-f8d39975645f" = true
+
+# all lowercase property
+"6c02a24e-6323-4ed5-9962-187d19e36bc8" = true
+
+# upper and lowercase property
+"8772d2b1-3c57-405a-93ac-0703b671adc1" = true
+
+# two nodes
+"a759b652-240e-42ec-a6d2-3a08d834b9e2" = true
+
+# two child trees
+"cc7c02bc-6097-42c4-ab88-a07cb1533d00" = true
+
+# multiple property values
+"724eeda6-00db-41b1-8aa9-4d5238ca0130" = true
+
+# escaped property
+"11c36323-93fc-495d-bb23-c88ee5844b8c" = true
diff --git a/exercises/sieve/.meta/tests.toml b/exercises/sieve/.meta/tests.toml
new file mode 100644
index 000000000..efe8c0bd0
--- /dev/null
+++ b/exercises/sieve/.meta/tests.toml
@@ -0,0 +1,16 @@
+[canonical-tests]
+
+# no primes under two
+"88529125-c4ce-43cc-bb36-1eb4ddd7b44f" = true
+
+# find first prime
+"4afe9474-c705-4477-9923-840e1024cc2b" = true
+
+# find primes up to 10
+"974945d8-8cd9-4f00-9463-7d813c7f17b7" = true
+
+# limit is prime
+"2e2417b7-3f3a-452a-8594-b9af08af6d82" = true
+
+# find primes up to 1000
+"92102a05-4c7c-47de-9ed0-b7d5fcd00f21" = true
diff --git a/exercises/simple-cipher/.meta/tests.toml b/exercises/simple-cipher/.meta/tests.toml
new file mode 100644
index 000000000..5bc43b889
--- /dev/null
+++ b/exercises/simple-cipher/.meta/tests.toml
@@ -0,0 +1,37 @@
+[canonical-tests]
+
+# Can encode
+"b8bdfbe1-bea3-41bb-a999-b41403f2b15d" = true
+
+# Can decode
+"3dff7f36-75db-46b4-ab70-644b3f38b81c" = true
+
+# Is reversible. I.e., if you apply decode in a encoded result, you must see the same plaintext encode parameter as a result of the decode method
+"8143c684-6df6-46ba-bd1f-dea8fcb5d265" = true
+
+# Key is made only of lowercase letters
+"defc0050-e87d-4840-85e4-51a1ab9dd6aa" = true
+
+# Can encode
+"565e5158-5b3b-41dd-b99d-33b9f413c39f" = true
+
+# Can decode
+"d44e4f6a-b8af-4e90-9d08-fd407e31e67b" = true
+
+# Is reversible. I.e., if you apply decode in a encoded result, you must see the same plaintext encode parameter as a result of the decode method
+"70a16473-7339-43df-902d-93408c69e9d1" = true
+
+# Can double shift encode
+"69a1458b-92a6-433a-a02d-7beac3ea91f9" = true
+
+# Can wrap on encode
+"21d207c1-98de-40aa-994f-86197ae230fb" = true
+
+# Can wrap on decode
+"a3d7a4d7-24a9-4de6-bdc4-a6614ced0cb3" = true
+
+# Can encode messages longer than the key
+"e31c9b8c-8eb6-45c9-a4b5-8344a36b9641" = true
+
+# Can decode messages longer than the key
+"93cfaae0-17da-4627-9a04-d6d1e1be52e3" = true
diff --git a/exercises/space-age/.meta/tests.toml b/exercises/space-age/.meta/tests.toml
new file mode 100644
index 000000000..52de49cf2
--- /dev/null
+++ b/exercises/space-age/.meta/tests.toml
@@ -0,0 +1,25 @@
+[canonical-tests]
+
+# age on Earth
+"84f609af-5a91-4d68-90a3-9e32d8a5cd34" = true
+
+# age on Mercury
+"ca20c4e9-6054-458c-9312-79679ffab40b" = true
+
+# age on Venus
+"502c6529-fd1b-41d3-8fab-65e03082b024" = true
+
+# age on Mars
+"9ceadf5e-a0d5-4388-9d40-2c459227ceb8" = true
+
+# age on Jupiter
+"42927dc3-fe5e-4f76-a5b5-f737fc19bcde" = true
+
+# age on Saturn
+"8469b332-7837-4ada-b27c-00ee043ebcad" = true
+
+# age on Uranus
+"999354c1-76f8-4bb5-a672-f317b6436743" = true
+
+# age on Neptune
+"80096d30-a0d4-4449-903e-a381178355d8" = true
diff --git a/exercises/spiral-matrix/.meta/tests.toml b/exercises/spiral-matrix/.meta/tests.toml
new file mode 100644
index 000000000..f0868d377
--- /dev/null
+++ b/exercises/spiral-matrix/.meta/tests.toml
@@ -0,0 +1,19 @@
+[canonical-tests]
+
+# empty spiral
+"8f584201-b446-4bc9-b132-811c8edd9040" = true
+
+# trivial spiral
+"e40ae5f3-e2c9-4639-8116-8a119d632ab2" = true
+
+# spiral of size 2
+"cf05e42d-eb78-4098-a36e-cdaf0991bc48" = true
+
+# spiral of size 3
+"1c475667-c896-4c23-82e2-e033929de939" = true
+
+# spiral of size 4
+"05ccbc48-d891-44f5-9137-f4ce462a759d" = true
+
+# spiral of size 5
+"f4d2165b-1738-4e0c-bed0-c459045ae50d" = true
diff --git a/exercises/sublist/.meta/tests.toml b/exercises/sublist/.meta/tests.toml
new file mode 100644
index 000000000..9a06e444a
--- /dev/null
+++ b/exercises/sublist/.meta/tests.toml
@@ -0,0 +1,52 @@
+[canonical-tests]
+
+# empty lists
+"97319c93-ebc5-47ab-a022-02a1980e1d29" = true
+
+# empty list within non empty list
+"de27dbd4-df52-46fe-a336-30be58457382" = true
+
+# non empty list contains empty list
+"5487cfd1-bc7d-429f-ac6f-1177b857d4fb" = true
+
+# list equals itself
+"1f390b47-f6b2-4a93-bc23-858ba5dda9a6" = true
+
+# different lists
+"7ed2bfb2-922b-4363-ae75-f3a05e8274f5" = true
+
+# false start
+"3b8a2568-6144-4f06-b0a1-9d266b365341" = true
+
+# consecutive
+"dc39ed58-6311-4814-be30-05a64bc8d9b1" = true
+
+# sublist at start
+"d1270dab-a1ce-41aa-b29d-b3257241ac26" = true
+
+# sublist in middle
+"81f3d3f7-4f25-4ada-bcdc-897c403de1b6" = true
+
+# sublist at end
+"43bcae1e-a9cf-470e-923e-0946e04d8fdd" = true
+
+# at start of superlist
+"76cf99ed-0ff0-4b00-94af-4dfb43fe5caa" = true
+
+# in middle of superlist
+"b83989ec-8bdf-4655-95aa-9f38f3e357fd" = true
+
+# at end of superlist
+"26f9f7c3-6cf6-4610-984a-662f71f8689b" = true
+
+# first list missing element from second list
+"0a6db763-3588-416a-8f47-76b1cedde31e" = true
+
+# second list missing element from first list
+"83ffe6d8-a445-4a3c-8795-1e51a95e65c3" = true
+
+# order matters to a list
+"0d7ee7c1-0347-45c8-9ef5-b88db152b30b" = true
+
+# same digits but different numbers
+"5f47ce86-944e-40f9-9f31-6368aad70aa6" = true
diff --git a/exercises/sum-of-multiples/.meta/tests.toml b/exercises/sum-of-multiples/.meta/tests.toml
new file mode 100644
index 000000000..49cb12320
--- /dev/null
+++ b/exercises/sum-of-multiples/.meta/tests.toml
@@ -0,0 +1,49 @@
+[canonical-tests]
+
+# no multiples within limit
+"54aaab5a-ce86-4edc-8b40-d3ab2400a279" = true
+
+# one factor has multiples within limit
+"361e4e50-c89b-4f60-95ef-5bc5c595490a" = true
+
+# more than one multiple within limit
+"e644e070-040e-4ae0-9910-93c69fc3f7ce" = true
+
+# more than one factor with multiples within limit
+"607d6eb9-535c-41ce-91b5-3a61da3fa57f" = true
+
+# each multiple is only counted once
+"f47e8209-c0c5-4786-b07b-dc273bf86b9b" = true
+
+# a much larger limit
+"28c4b267-c980-4054-93e9-07723db615ac" = true
+
+# three factors
+"09c4494d-ff2d-4e0f-8421-f5532821ee12" = true
+
+# factors not relatively prime
+"2d0d5faa-f177-4ad6-bde9-ebb865083751" = true
+
+# some pairs of factors relatively prime and some not
+"ece8f2e8-96aa-4166-bbb7-6ce71261e354" = true
+
+# one factor is a multiple of another
+"624fdade-6ffb-400e-8472-456a38c171c0" = true
+
+# much larger factors
+"949ee7eb-db51-479c-b5cb-4a22b40ac057" = true
+
+# all numbers are multiples of 1
+"41093673-acbd-482c-ab80-d00a0cbedecd" = true
+
+# no factors means an empty sum
+"1730453b-baaa-438e-a9c2-d754497b2a76" = true
+
+# the only multiple of 0 is 0
+"214a01e9-f4bf-45bb-80f1-1dce9fbb0310" = true
+
+# the factor 0 does not affect the sum of multiples of other factors
+"c423ae21-a0cb-4ec7-aeb1-32971af5b510" = true
+
+# solutions using include-exclude must extend to cardinality greater than 3
+"17053ba9-112f-4ac0-aadb-0519dd836342" = true
diff --git a/exercises/transpose/.meta/tests.toml b/exercises/transpose/.meta/tests.toml
new file mode 100644
index 000000000..715a1504b
--- /dev/null
+++ b/exercises/transpose/.meta/tests.toml
@@ -0,0 +1,34 @@
+[canonical-tests]
+
+# empty string
+"404b7262-c050-4df0-a2a2-0cb06cd6a821" = true
+
+# two characters in a row
+"a89ce8a3-c940-4703-a688-3ea39412fbcb" = true
+
+# two characters in a column
+"855bb6ae-4180-457c-abd0-ce489803ce98" = true
+
+# simple
+"5ceda1c0-f940-441c-a244-0ced197769c8" = true
+
+# single line
+"a54675dd-ae7d-4a58-a9c4-0c20e99a7c1f" = true
+
+# first line longer than second line
+"0dc2ec0b-549d-4047-aeeb-8029fec8d5c5" = true
+
+# second line longer than first line
+"984e2ec3-b3d3-4b53-8bd6-96f5ef404102" = true
+
+# mixed line length
+"eccd3784-45f0-4a3f-865a-360cb323d314" = true
+
+# square
+"85b96b3f-d00c-4f80-8ca2-c8a5c9216c2d" = true
+
+# rectangle
+"b9257625-7a53-4748-8863-e08e9d27071d" = true
+
+# triangle
+"b80badc9-057e-4543-bd07-ce1296a1ea2c" = true
diff --git a/exercises/triangle/.meta/tests.toml b/exercises/triangle/.meta/tests.toml
new file mode 100644
index 000000000..cf84bc422
--- /dev/null
+++ b/exercises/triangle/.meta/tests.toml
@@ -0,0 +1,64 @@
+[canonical-tests]
+
+# Some tests are deliberately not included,
+# as the Haskell track returns a `data`,
+# such that a triangle can only be one of the three types,
+# rather than equilateral triangles also being isosceles
+# https://github.com/exercism/haskell/pull/484
+
+# all sides are equal
+"8b2c43ac-7257-43f9-b552-7631a91988af" = true
+
+# any side is unequal
+"33eb6f87-0498-4ccf-9573-7f8c3ce92b7b" = false
+
+# no sides are equal
+"c6585b7d-a8c0-4ad8-8a34-e21d36f7ad87" = true
+
+# all zero sides is not a triangle
+"16e8ceb0-eadb-46d1-b892-c50327479251" = true
+
+# sides may be floats
+"3022f537-b8e5-4cc1-8f12-fd775827a00c" = false
+
+# last two sides are equal
+"cbc612dc-d75a-4c1c-87fc-e2d5edd70b71" = true
+
+# first two sides are equal
+"e388ce93-f25e-4daf-b977-4b7ede992217" = true
+
+# first and last sides are equal
+"d2080b79-4523-4c3f-9d42-2da6e81ab30f" = true
+
+# equilateral triangles are also isosceles
+"8d71e185-2bd7-4841-b7e1-71689a5491d8" = false
+
+# no sides are equal
+"840ed5f8-366f-43c5-ac69-8f05e6f10bbb" = false
+
+# first triangle inequality violation
+"2eba0cfb-6c65-4c40-8146-30b608905eae" = true
+
+# second triangle inequality violation
+"278469cb-ac6b-41f0-81d4-66d9b828f8ac" = false
+
+# third triangle inequality violation
+"90efb0c7-72bb-4514-b320-3a3892e278ff" = false
+
+# sides may be floats
+"adb4ee20-532f-43dc-8d31-e9271b7ef2bc" = false
+
+# no sides are equal
+"e8b5f09c-ec2e-47c1-abec-f35095733afb" = true
+
+# all sides are equal
+"2510001f-b44d-4d18-9872-2303e7977dc1" = false
+
+# two sides are equal
+"c6e15a92-90d9-4fb3-90a2-eef64f8d3e1e" = true
+
+# may not violate triangle inequality
+"70ad5154-0033-48b7-af2c-b8d739cd9fdc" = true
+
+# sides may be floats
+"26d9d59d-f8f1-40d3-ad58-ae4d54123d7d" = true
diff --git a/exercises/trinary/.meta/tests.toml b/exercises/trinary/.meta/tests.toml
new file mode 100644
index 000000000..77f66278d
--- /dev/null
+++ b/exercises/trinary/.meta/tests.toml
@@ -0,0 +1,34 @@
+[canonical-tests]
+
+# trinary 1 is decimal 1
+"a7a79a9e-5606-454c-9cdb-4f3c0ca46931" = true
+
+# trinary 2 is decimal 2
+"39240078-13e2-4eb8-87c4-aeffa7d64130" = true
+
+# trinary 10 is decimal 3
+"81900d67-7e07-4d41-a71e-86f1cd72ce1f" = true
+
+# trinary 11 is decimal 4
+"7a8d5341-f88a-4c60-9048-4d5e017fa701" = true
+
+# trinary 100 is decimal 9
+"6b3c37f6-d6b3-4575-85c0-19f48dd101af" = true
+
+# trinary 112 is decimal 14
+"a210b2b8-d333-4e19-9e59-87cabdd2a0ba" = true
+
+# trinary 222 is decimal 26
+"5ae03472-b942-42ce-ba00-e84a7dc86dd8" = true
+
+# trinary 1122000120 is decimal 32091
+"d4fabf94-6149-4d1e-b42f-b34dc3ddef8f" = true
+
+# invalid trinary digits returns 0
+"34be152d-38f3-4dcf-b5ab-9e14fe2f7161" = true
+
+# invalid word as input returns 0
+"b57aa24d-3da2-4787-9429-5bc94d3112d6" = true
+
+# invalid numbers with letters as input returns 0
+"673c2057-5d89-483c-87fa-139da6927b90" = true
diff --git a/exercises/twelve-days/.meta/tests.toml b/exercises/twelve-days/.meta/tests.toml
new file mode 100644
index 000000000..ee4722d4c
--- /dev/null
+++ b/exercises/twelve-days/.meta/tests.toml
@@ -0,0 +1,46 @@
+[canonical-tests]
+
+# first day a partridge in a pear tree
+"c0b5a5e6-c89d-49b1-a6b2-9f523bff33f7" = true
+
+# second day two turtle doves
+"1c64508a-df3d-420a-b8e1-fe408847854a" = true
+
+# third day three french hens
+"a919e09c-75b2-4e64-bb23-de4a692060a8" = true
+
+# fourth day four calling birds
+"9bed8631-ec60-4894-a3bb-4f0ec9fbe68d" = true
+
+# fifth day five gold rings
+"cf1024f0-73b6-4545-be57-e9cea565289a" = true
+
+# sixth day six geese-a-laying
+"50bd3393-868a-4f24-a618-68df3d02ff04" = true
+
+# seventh day seven swans-a-swimming
+"8f29638c-9bf1-4680-94be-e8b84e4ade83" = true
+
+# eighth day eight maids-a-milking
+"7038d6e1-e377-47ad-8c37-10670a05bc05" = true
+
+# ninth day nine ladies dancing
+"37a800a6-7a56-4352-8d72-0f51eb37cfe8" = true
+
+# tenth day ten lords-a-leaping
+"10b158aa-49ff-4b2d-afc3-13af9133510d" = true
+
+# eleventh day eleven pipers piping
+"08d7d453-f2ba-478d-8df0-d39ea6a4f457" = true
+
+# twelfth day twelve drummers drumming
+"0620fea7-1704-4e48-b557-c05bf43967f0" = true
+
+# recites first three verses of the song
+"da8b9013-b1e8-49df-b6ef-ddec0219e398" = true
+
+# recites three verses from the middle of the song
+"c095af0d-3137-4653-ad32-bfb899eda24c" = true
+
+# recites the whole song
+"20921bc9-cc52-4627-80b3-198cbbfcf9b7" = true
diff --git a/exercises/word-count/.meta/tests.toml b/exercises/word-count/.meta/tests.toml
new file mode 100644
index 000000000..e3b34ac07
--- /dev/null
+++ b/exercises/word-count/.meta/tests.toml
@@ -0,0 +1,40 @@
+[canonical-tests]
+
+# count one word
+"61559d5f-2cad-48fb-af53-d3973a9ee9ef" = true
+
+# count one of each word
+"5abd53a3-1aed-43a4-a15a-29f88c09cbbd" = true
+
+# multiple occurrences of a word
+"2a3091e5-952e-4099-9fac-8f85d9655c0e" = true
+
+# handles cramped lists
+"e81877ae-d4da-4af4-931c-d923cd621ca6" = true
+
+# handles expanded lists
+"7349f682-9707-47c0-a9af-be56e1e7ff30" = true
+
+# ignore punctuation
+"a514a0f2-8589-4279-8892-887f76a14c82" = true
+
+# include numbers
+"d2e5cee6-d2ec-497b-bdc9-3ebe092ce55e" = true
+
+# normalize case
+"dac6bc6a-21ae-4954-945d-d7f716392dbf" = true
+
+# with apostrophes
+"4185a902-bdb0-4074-864c-f416e42a0f19" = true
+
+# with quotations
+"be72af2b-8afe-4337-b151-b297202e4a7b" = true
+
+# substrings from the beginning
+"8d6815fe-8a51-4a65-96f9-2fb3f6dc6ed6" = true
+
+# multiple spaces not detected as a word
+"c5f4ef26-f3f7-4725-b314-855c04fb4c13" = true
+
+# alternating word separators not detected as a word
+"50176e8a-fe8e-4f4c-b6b6-aa9cf8f20360" = true
diff --git a/exercises/wordy/.meta/tests.toml b/exercises/wordy/.meta/tests.toml
new file mode 100644
index 000000000..c3e3faa27
--- /dev/null
+++ b/exercises/wordy/.meta/tests.toml
@@ -0,0 +1,70 @@
+[canonical-tests]
+
+# just a number
+"88bf4b28-0de3-4883-93c7-db1b14aa806e" = true
+
+# addition
+"bb8c655c-cf42-4dfc-90e0-152fcfd8d4e0" = true
+
+# more addition
+"79e49e06-c5ae-40aa-a352-7a3a01f70015" = true
+
+# addition with negative numbers
+"b345dbe0-f733-44e1-863c-5ae3568f3803" = true
+
+# large addition
+"cd070f39-c4cc-45c4-97fb-1be5e5846f87" = true
+
+# subtraction
+"0d86474a-cd93-4649-a4fa-f6109a011191" = true
+
+# multiplication
+"30bc8395-5500-4712-a0cf-1d788a529be5" = true
+
+# division
+"34c36b08-8605-4217-bb57-9a01472c427f" = true
+
+# multiple additions
+"da6d2ce4-fb94-4d26-8f5f-b078adad0596" = true
+
+# addition and subtraction
+"7fd74c50-9911-4597-be09-8de7f2fea2bb" = true
+
+# multiple subtraction
+"b120ffd5-bad6-4e22-81c8-5512e8faf905" = true
+
+# subtraction then addition
+"4f4a5749-ef0c-4f60-841f-abcfaf05d2ae" = true
+
+# multiple multiplication
+"312d908c-f68f-42c9-aa75-961623cc033f" = true
+
+# addition and multiplication
+"38e33587-8940-4cc1-bc28-bfd7e3966276" = true
+
+# multiple division
+"3c854f97-9311-46e8-b574-92b60d17d394" = true
+
+# unknown operation
+"3ad3e433-8af7-41ec-aa9b-97b42ab49357" = true
+
+# Non math question
+"8a7e85a8-9e7b-4d46-868f-6d759f4648f8" = true
+
+# reject problem missing an operand
+"42d78b5f-dbd7-4cdb-8b30-00f794bb24cf" = true
+
+# reject problem with no operands or operators
+"c2c3cbfc-1a72-42f2-b597-246e617e66f5" = true
+
+# reject two operations in a row
+"4b3df66d-6ed5-4c95-a0a1-d38891fbdab6" = true
+
+# reject two numbers in a row
+"6abd7a50-75b4-4665-aa33-2030fd08bab1" = true
+
+# reject postfix notation
+"10a56c22-e0aa-405f-b1d2-c642d9c4c9de" = true
+
+# reject prefix notation
+"0035bc63-ac43-4bb5-ad6d-e8651b7d954e" = true
diff --git a/exercises/yacht/.meta/tests.toml b/exercises/yacht/.meta/tests.toml
new file mode 100644
index 000000000..459f97a78
--- /dev/null
+++ b/exercises/yacht/.meta/tests.toml
@@ -0,0 +1,85 @@
+[canonical-tests]
+
+# Yacht
+"3060e4a5-4063-4deb-a380-a630b43a84b6" = true
+
+# Not Yacht
+"15026df2-f567-482f-b4d5-5297d57769d9" = true
+
+# Ones
+"36b6af0c-ca06-4666-97de-5d31213957a4" = true
+
+# Ones, out of order
+"023a07c8-6c6e-44d0-bc17-efc5e1b8205a" = true
+
+# No ones
+"7189afac-cccd-4a74-8182-1cb1f374e496" = true
+
+# Twos
+"793c4292-dd14-49c4-9707-6d9c56cee725" = true
+
+# Fours
+"dc41bceb-d0c5-4634-a734-c01b4233a0c6" = true
+
+# Yacht counted as threes
+"f6125417-5c8a-4bca-bc5b-b4b76d0d28c8" = true
+
+# Yacht of 3s counted as fives
+"464fc809-96ed-46e4-acb8-d44e302e9726" = true
+
+# Sixes
+"e8a036e0-9d21-443a-8b5f-e15a9e19a761" = true
+
+# Full house two small, three big
+"51cb26db-6b24-49af-a9ff-12f53b252eea" = true
+
+# Full house three small, two big
+"1822ca9d-f235-4447-b430-2e8cfc448f0c" = true
+
+# Two pair is not a full house
+"b208a3fc-db2e-4363-a936-9e9a71e69c07" = true
+
+# Four of a kind is not a full house
+"b90209c3-5956-445b-8a0b-0ac8b906b1c2" = true
+
+# Yacht is not a full house
+"32a3f4ee-9142-4edf-ba70-6c0f96eb4b0c" = true
+
+# Four of a Kind
+"b286084d-0568-4460-844a-ba79d71d79c6" = true
+
+# Yacht can be scored as Four of a Kind
+"f25c0c90-5397-4732-9779-b1e9b5f612ca" = true
+
+# Full house is not Four of a Kind
+"9f8ef4f0-72bb-401a-a871-cbad39c9cb08" = true
+
+# Little Straight
+"b4743c82-1eb8-4a65-98f7-33ad126905cd" = true
+
+# Little Straight as Big Straight
+"7ac08422-41bf-459c-8187-a38a12d080bc" = true
+
+# Four in order but not a little straight
+"97bde8f7-9058-43ea-9de7-0bc3ed6d3002" = true
+
+# No pairs but not a little straight
+"cef35ff9-9c5e-4fd2-ae95-6e4af5e95a99" = true
+
+# Minimum is 1, maximum is 5, but not a little straight
+"fd785ad2-c060-4e45-81c6-ea2bbb781b9d" = true
+
+# Big Straight
+"35bd74a6-5cf6-431a-97a3-4f713663f467" = true
+
+# Big Straight as little straight
+"87c67e1e-3e87-4f3a-a9b1-62927822b250" = true
+
+# No pairs but not a big straight
+"c1fa0a3a-40ba-4153-a42d-32bc34d2521e" = true
+
+# Choice
+"207e7300-5d10-43e5-afdd-213e3ac8827d" = true
+
+# Yacht as choice
+"b524c0cf-32d2-4b40-8fb3-be3500f3f135" = true
diff --git a/exercises/zebra-puzzle/.meta/tests.toml b/exercises/zebra-puzzle/.meta/tests.toml
new file mode 100644
index 000000000..53a5d8ae6
--- /dev/null
+++ b/exercises/zebra-puzzle/.meta/tests.toml
@@ -0,0 +1,7 @@
+[canonical-tests]
+
+# resident who drinks water
+"16efb4e4-8ad7-4d5e-ba96-e5537b66fd42" = true
+
+# resident who owns zebra
+"084d5b8b-24e2-40e6-b008-c800da8cd257" = true
diff --git a/exercises/zipper/.meta/tests.toml b/exercises/zipper/.meta/tests.toml
new file mode 100644
index 000000000..fba800be4
--- /dev/null
+++ b/exercises/zipper/.meta/tests.toml
@@ -0,0 +1,40 @@
+[canonical-tests]
+
+# data is retained
+"771c652e-0754-4ef0-945c-0675d12ef1f5" = true
+
+# left, right and value
+"d7dcbb92-47fc-4d01-b81a-df3353bc09ff" = true
+
+# dead end
+"613d8286-b05c-4453-b205-e6f9c5966339" = true
+
+# tree from deep focus
+"dda31af7-1c68-4e29-933a-c9d198d94284" = true
+
+# traversing up from top
+"1e3072a6-f85b-430b-b014-cdb4087e3577" = true
+
+# left, right, and up
+"b8505f6a-aed4-4c2e-824f-a0ed8570d74b" = true
+
+# set_value
+"47df1a27-b709-496e-b381-63a03b82ea5f" = true
+
+# set_value after traversing up
+"16a1f1a8-dbed-456d-95ac-1cbb6093e0ab" = true
+
+# set_left with leaf
+"535a91af-a02e-49cd-8d2c-ecb6e4647174" = true
+
+# set_right with null
+"b3f60c4b-a788-4ffd-be5d-1e69aee61de3" = true
+
+# set_right with subtree
+"e91c221d-7b90-4604-b4ec-46638a673a12" = true
+
+# set_value on deep focus
+"c246be85-6648-4e9c-866f-b08cd495149a" = true
+
+# different paths to same zipper
+"47aa85a0-5240-48a4-9f42-e2ac636710ea" = true