specproof 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +10 -0
- data/LICENSE.txt +21 -0
- data/README.md +283 -0
- data/docs/agent-skills.md +60 -0
- data/docs/benchmark.md +24 -0
- data/docs/constrained-partition-pairs-results.md +44 -0
- data/docs/finite-strings-results.md +75 -0
- data/docs/implementation-status.md +65 -0
- data/docs/input-selection-results.md +123 -0
- data/docs/lint-cleanup-plan.md +12 -0
- data/docs/method-targets-results.md +68 -0
- data/docs/minitest-compatibility.md +33 -0
- data/docs/optional-arguments-plan.md +25 -0
- data/docs/optional-arguments-results.md +45 -0
- data/docs/pairwise-partitions-results.md +53 -0
- data/docs/pairwise-results.md +77 -0
- data/docs/pairwise-suggestions-results.md +104 -0
- data/docs/rbs-implementation-status.md +115 -0
- data/docs/rbs-string-literals-results.md +58 -0
- data/docs/real-project-pilot.md +84 -0
- data/docs/report-format.md +244 -0
- data/examples/age_policy/README.md +39 -0
- data/examples/age_policy/faulty/lib/age_policy.rb +9 -0
- data/examples/age_policy/faulty/test/age_policy_examples_test.rb +17 -0
- data/examples/age_policy/faulty/test/age_policy_test.rb +21 -0
- data/examples/age_policy/lib/age_policy.rb +8 -0
- data/examples/age_policy/run.rb +23 -0
- data/examples/age_policy/specification.rb +8 -0
- data/examples/age_policy/test/age_policy_test.rb +18 -0
- data/examples/constrained_partition_pairs/README.md +40 -0
- data/examples/constrained_partition_pairs/lib/account_eligibility.rb +8 -0
- data/examples/constrained_partition_pairs/requirements.md +11 -0
- data/examples/constrained_partition_pairs/run.rb +22 -0
- data/examples/constrained_partition_pairs/specification.rb +17 -0
- data/examples/constrained_partition_pairs/test_account_eligibility.rb +34 -0
- data/examples/input_selection/README.md +22 -0
- data/examples/input_selection/delivery_policy.rb +11 -0
- data/examples/input_selection/requirements.md +9 -0
- data/examples/input_selection/specification.rb +8 -0
- data/examples/input_selection/test_delivery_policy.rb +38 -0
- data/examples/method_targets/README.md +30 -0
- data/examples/method_targets/lib/delivery_defaults.rb +8 -0
- data/examples/method_targets/lib/delivery_options.rb +12 -0
- data/examples/method_targets/requirements.md +20 -0
- data/examples/method_targets/specification_objects.rb +17 -0
- data/examples/method_targets/specification_strings.rb +15 -0
- data/examples/pairwise/README.md +46 -0
- data/examples/pairwise/constrained_requirements.md +6 -0
- data/examples/pairwise/constrained_specification.rb +11 -0
- data/examples/pairwise/corrected/lib/delivery_options.rb +11 -0
- data/examples/pairwise/corrected/test/delivery_options_test.rb +28 -0
- data/examples/pairwise/faulty/lib/delivery_options.rb +11 -0
- data/examples/pairwise/faulty/test/gap_delivery_options_test.rb +10 -0
- data/examples/pairwise/faulty/test/initial_delivery_options_test.rb +23 -0
- data/examples/pairwise/requirements.md +11 -0
- data/examples/pairwise/run.rb +36 -0
- data/examples/pairwise/specification.rb +9 -0
- data/examples/pairwise/three_boolean/lib/boolean_batch.rb +8 -0
- data/examples/pairwise/three_boolean/test/boolean_batch_test.rb +28 -0
- data/examples/pairwise/three_boolean_specification.rb +10 -0
- data/examples/pairwise_partitions/README.md +34 -0
- data/examples/pairwise_partitions/lib/age_policy.rb +8 -0
- data/examples/pairwise_partitions/requirements.md +8 -0
- data/examples/pairwise_partitions/run.rb +22 -0
- data/examples/pairwise_partitions/specification.rb +13 -0
- data/examples/pairwise_partitions/test_age_policy.rb +29 -0
- data/examples/pairwise_suggestions/README.md +48 -0
- data/examples/pairwise_suggestions/run.rb +55 -0
- data/examples/pairwise_suggestions/three_boolean/lib/suggested_boolean_batch.rb +8 -0
- data/examples/pairwise_suggestions/three_boolean/requirements.md +7 -0
- data/examples/pairwise_suggestions/three_boolean/specification.rb +11 -0
- data/examples/pairwise_suggestions/three_boolean/test/baseline_test.rb +9 -0
- data/examples/pairwise_suggestions/three_boolean/test/complete_test.rb +22 -0
- data/examples/partition_gap.rb +8 -0
- data/examples/rbs_registration/README.md +58 -0
- data/examples/rbs_registration/implementations/age_gt/registration.rb +8 -0
- data/examples/rbs_registration/implementations/age_gte/registration.rb +8 -0
- data/examples/rbs_registration/requirements.md +9 -0
- data/examples/rbs_registration/sig/registration.rbs +3 -0
- data/examples/rbs_registration/specification.rb +10 -0
- data/examples/rbs_registration/test_registration.rb +43 -0
- data/examples/rbs_string_literals/README.md +34 -0
- data/examples/rbs_string_literals/lib/delivery_options.rb +9 -0
- data/examples/rbs_string_literals/requirements.md +11 -0
- data/examples/rbs_string_literals/sig/delivery_options.rbs +3 -0
- data/examples/rbs_string_literals/specification.rb +7 -0
- data/examples/rbs_string_literals/test_delivery_options.rb +46 -0
- data/examples/shipping.rb +12 -0
- data/exe/specproof +7 -0
- data/lib/specproof/checker.rb +144 -0
- data/lib/specproof/cli.rb +292 -0
- data/lib/specproof/collector.rb +304 -0
- data/lib/specproof/combinations.rb +226 -0
- data/lib/specproof/composer.rb +97 -0
- data/lib/specproof/coverage.rb +229 -0
- data/lib/specproof/coverage_report.rb +334 -0
- data/lib/specproof/domains.rb +124 -0
- data/lib/specproof/loader.rb +285 -0
- data/lib/specproof/minitest.rb +443 -0
- data/lib/specproof/model.rb +198 -0
- data/lib/specproof/partition_space.rb +268 -0
- data/lib/specproof/planner.rb +125 -0
- data/lib/specproof/report.rb +248 -0
- data/lib/specproof/skills.rb +188 -0
- data/lib/specproof/sources/rbs.rb +137 -0
- data/lib/specproof/sources/rbs_projector.rb +268 -0
- data/lib/specproof/suggestion_report.rb +159 -0
- data/lib/specproof/suggestions.rb +352 -0
- data/lib/specproof/target_name.rb +100 -0
- data/lib/specproof/type_constraints.rb +76 -0
- data/lib/specproof/version.rb +7 -0
- data/lib/specproof.rb +100 -0
- data/sig/specproof.rbs +222 -0
- data/skills/specproof-specify/SKILL.md +282 -0
- data/skills/specproof-specify/references/usage.md +265 -0
- data/skills/specproof-test-gaps/SKILL.md +214 -0
- data/skills/specproof-test-gaps/references/usage.md +231 -0
- metadata +165 -0
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Exercises all pairs among three boolean inputs with four calls.
|
|
4
|
+
class BooleanBatchTest < Minitest::Test
|
|
5
|
+
def setup
|
|
6
|
+
@batch = BooleanBatch.new
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def test_all_inputs_false
|
|
10
|
+
assert_equal([false, false, false],
|
|
11
|
+
@batch.build(first: false, second: false, third: false))
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def test_first_false_other_inputs_true
|
|
15
|
+
assert_equal([false, true, true],
|
|
16
|
+
@batch.build(first: false, second: true, third: true))
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def test_first_and_third_true
|
|
20
|
+
assert_equal([true, false, true],
|
|
21
|
+
@batch.build(first: true, second: false, third: true))
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def test_first_and_second_true
|
|
25
|
+
assert_equal([true, true, false],
|
|
26
|
+
@batch.build(first: true, second: true, third: false))
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "specproof"
|
|
4
|
+
|
|
5
|
+
SpecProof.define "BooleanBatch#build" do
|
|
6
|
+
input :first, values: [false, true]
|
|
7
|
+
input :second, values: [false, true]
|
|
8
|
+
input :third, values: [false, true]
|
|
9
|
+
combinations :first, :second, :third, all_allowed: true
|
|
10
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Pairwise coverage by integer partitions
|
|
2
|
+
|
|
3
|
+
This example declares an age range split into the complete, non-overlapping
|
|
4
|
+
categories `minor` (`0...18`) and `adult` (`18..120`), plus the finite country
|
|
5
|
+
values `"ro"` and `"de"`. All combinations are allowed. The independent
|
|
6
|
+
[requirement](requirements.md) says eligibility requires age 18 or older and
|
|
7
|
+
does not depend on country.
|
|
8
|
+
|
|
9
|
+
Run from the `gems/specproof` directory with the prepared Ruby and bundle for
|
|
10
|
+
this checkout. Reports and suggestion output go under `tmp/`:
|
|
11
|
+
|
|
12
|
+
```sh
|
|
13
|
+
bundle exec ruby -Ilib exe/specproof plan examples/pairwise_partitions/specification.rb --criterion pairwise-partitions-v1 --format json
|
|
14
|
+
bundle exec ruby examples/pairwise_partitions/run.rb initial
|
|
15
|
+
bundle exec ruby -Ilib exe/specproof suggest examples/pairwise_partitions/specification.rb --report tmp/pairwise-partitions-initial.json --format json > tmp/pairwise-partitions-suggestions.json
|
|
16
|
+
bundle exec ruby examples/pairwise_partitions/run.rb complete
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
The plan has four category-pair obligations: minor/adult crossed with each
|
|
20
|
+
country. Its typed partition values include a name and inclusive bounds, such
|
|
21
|
+
as `{ "kind": "partition", "name": "minor", "lower": "0", "upper": "17" }`.
|
|
22
|
+
The initial process passes three requirement-backed calls and leaves
|
|
23
|
+
minor/`"de"` unobserved. `suggest` reads the criterion from that validated
|
|
24
|
+
coverage report and proposes the concrete input `age: 0, country: "de"` using
|
|
25
|
+
the partition's lower endpoint. The suggestion supplies an input only. The
|
|
26
|
+
complete process adds a separate assertion, justified by the under-18 rule,
|
|
27
|
+
and exercises all four category pairs.
|
|
28
|
+
|
|
29
|
+
Category coverage counts any in-domain age in the declared category. It is not
|
|
30
|
+
boundary-value coverage, does not establish equivalence within a category, and
|
|
31
|
+
does not infer expected outputs. The assertion oracle remains the separate
|
|
32
|
+
requirement. Existing `pairwise-finite-v1` and scalar plans retain their
|
|
33
|
+
behavior; this criterion supports only integer ranges with explicit complete,
|
|
34
|
+
non-overlapping partitions and `all_allowed: true`.
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# Age eligibility requirement
|
|
2
|
+
|
|
3
|
+
For the supported countries `"ro"` and `"de"`, an applicant is eligible
|
|
4
|
+
exactly when their age is at least 18. Country does not change the age rule.
|
|
5
|
+
In particular, every applicant under 18 is ineligible.
|
|
6
|
+
|
|
7
|
+
This requirement supplies the expected results in the example tests. Pairwise
|
|
8
|
+
category coverage and a suggested input do not provide an output oracle.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "bundler/setup"
|
|
4
|
+
require "minitest/autorun"
|
|
5
|
+
require "specproof/minitest"
|
|
6
|
+
require_relative "lib/age_policy"
|
|
7
|
+
|
|
8
|
+
mode = ARGV.shift || "initial"
|
|
9
|
+
abort "Choose initial or complete" unless %w[initial complete].include?(mode)
|
|
10
|
+
|
|
11
|
+
ENV["ADD_MISSING_CATEGORY"] = mode == "complete" ? "1" : "0"
|
|
12
|
+
root = File.expand_path("../..", __dir__)
|
|
13
|
+
report = File.join(root, "tmp/pairwise-partitions-#{mode}.json")
|
|
14
|
+
|
|
15
|
+
SpecProof::Minitest.install!(
|
|
16
|
+
specifications: [File.join(__dir__, "specification.rb")],
|
|
17
|
+
criterion: "pairwise-partitions-v1",
|
|
18
|
+
output: report,
|
|
19
|
+
fail_on_missing: false
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
require_relative "test_age_policy"
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "specproof"
|
|
4
|
+
|
|
5
|
+
SpecProof.define "AgePolicy#eligible?" do
|
|
6
|
+
input :age, range: 0..120, allow_gaps: false, allow_overlaps: false do
|
|
7
|
+
partition :minor, range: 0...18
|
|
8
|
+
partition :adult, range: 18..120
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
input :country, values: %w[ro de]
|
|
12
|
+
combinations :age, :country, all_allowed: true
|
|
13
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "minitest/autorun"
|
|
4
|
+
require_relative "lib/age_policy"
|
|
5
|
+
|
|
6
|
+
# Covers selected age-category and country pairs.
|
|
7
|
+
class AgePolicyPairwiseTest < Minitest::Test
|
|
8
|
+
BASELINE_CASES = [
|
|
9
|
+
[16, "ro", false],
|
|
10
|
+
[30, "ro", true],
|
|
11
|
+
[30, "de", true]
|
|
12
|
+
].freeze
|
|
13
|
+
|
|
14
|
+
def test_baseline_calls_follow_the_age_requirement
|
|
15
|
+
policy = AgePolicy.new
|
|
16
|
+
|
|
17
|
+
BASELINE_CASES.each do |age, country, expected|
|
|
18
|
+
assert_equal expected, policy.eligible?(age: age, country: country),
|
|
19
|
+
"requirements.md: eligibility depends on age >= 18, not country"
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
if ENV["ADD_MISSING_CATEGORY"] == "1"
|
|
24
|
+
def test_minor_from_de_is_ineligible
|
|
25
|
+
refute AgePolicy.new.eligible?(age: 0, country: "de"),
|
|
26
|
+
"requirements.md: every applicant under 18 is ineligible"
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# Pairwise missing-pair suggestions
|
|
2
|
+
|
|
3
|
+
Run these commands from `gems/specproof` with its development bundle. Reports
|
|
4
|
+
and generated suggestion JSON go under the ignored `tmp/` directory. Each
|
|
5
|
+
runner command starts a new Ruby process and writes a fresh coverage report.
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
bundle exec ruby examples/pairwise_suggestions/run.rb delivery-baseline tmp/suggestion-delivery-baseline.json
|
|
9
|
+
bundle exec ruby -Ilib exe/specproof suggest examples/pairwise/specification.rb --report tmp/suggestion-delivery-baseline.json --format json > tmp/suggestion-delivery.json
|
|
10
|
+
bundle exec ruby examples/pairwise_suggestions/run.rb delivery-gap tmp/suggestion-delivery-gap.json
|
|
11
|
+
bundle exec ruby -Ilib exe/specproof suggest examples/pairwise/specification.rb --report tmp/suggestion-delivery-gap.json --format json > tmp/suggestion-after-gap.json
|
|
12
|
+
bundle exec ruby examples/pairwise_suggestions/run.rb delivery-corrected tmp/suggestion-delivery-corrected.json
|
|
13
|
+
bundle exec ruby examples/pairwise_suggestions/run.rb booleans-baseline tmp/suggestion-booleans-baseline.json
|
|
14
|
+
bundle exec ruby -Ilib exe/specproof suggest examples/pairwise_suggestions/three_boolean/specification.rb --report tmp/suggestion-booleans-baseline.json --format json > tmp/suggestion-booleans.json
|
|
15
|
+
bundle exec ruby examples/pairwise_suggestions/run.rb booleans-complete tmp/suggestion-booleans-complete.json
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
The DeliveryOptions declaration and [requirements](../pairwise/requirements.md)
|
|
19
|
+
come from the existing pairwise example. Its baseline tests pass while
|
|
20
|
+
exercising three of four pair obligations. The suggestion command returns an
|
|
21
|
+
allowed `expedited: true, notify: true` assignment and the stable ID it covers.
|
|
22
|
+
That row is a candidate only: the example's separate gap test asserts the
|
|
23
|
+
independent requirement that the notification result equals `notify`. The
|
|
24
|
+
assertion fails against the faulty implementation, even though its fresh
|
|
25
|
+
report has exercised all four pairs. The corrected implementation passes that
|
|
26
|
+
same assertion in a separate process and writes a different report.
|
|
27
|
+
Running `suggest` on the failing gap report is also valid: it returns no rows
|
|
28
|
+
because all pairs were reached, while preserving `source_tests_passed: false`.
|
|
29
|
+
|
|
30
|
+
For three booleans, the baseline observes only `false, false, false`, leaving
|
|
31
|
+
nine pair IDs missing. One full assignment in the suggestion covers multiple
|
|
32
|
+
missing pairs. The group requires no joint restrictions, and the independent
|
|
33
|
+
[requirements](three_boolean/requirements.md) define the expected result. The
|
|
34
|
+
suggestion output lists the deliberately excluded `context` input as
|
|
35
|
+
`unassigned_inputs`; callers must still provide it and satisfy its application
|
|
36
|
+
contract. The completion run uses the three remaining assignments from the
|
|
37
|
+
small four-row covering set and asserts the requirement for every call.
|
|
38
|
+
|
|
39
|
+
Never turn suggestion output directly into a test call with generated or
|
|
40
|
+
implementation-derived expected values. Compare the report with a fresh
|
|
41
|
+
pairwise plan: it must be completed and intact, with the current digest, exact
|
|
42
|
+
typed obligation denominator and scope, consistent counters and projections,
|
|
43
|
+
and no errors or unknown observations. A failed assertion result is accepted
|
|
44
|
+
and preserved. Filtered reports remain limited to their recorded scope. The
|
|
45
|
+
digest does not fingerprint implementation or tests, so run the suite again
|
|
46
|
+
after any such changes. The CLI validates the report before producing any
|
|
47
|
+
suggestions; oversized or inconsistent evidence yields an error without
|
|
48
|
+
partial results.
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "bundler/setup"
|
|
4
|
+
require "minitest/autorun"
|
|
5
|
+
require "specproof/minitest"
|
|
6
|
+
|
|
7
|
+
mode = ARGV.shift || "delivery-corrected"
|
|
8
|
+
output = ARGV.shift || File.expand_path("../../tmp/pairwise-suggestions-#{mode}.json", __dir__)
|
|
9
|
+
|
|
10
|
+
pairwise_root = File.expand_path("../pairwise", __dir__)
|
|
11
|
+
scenarios = {
|
|
12
|
+
"delivery-baseline" => {
|
|
13
|
+
implementation: "faulty/lib/delivery_options.rb",
|
|
14
|
+
specification: "specification.rb",
|
|
15
|
+
tests: ["faulty/test/initial_delivery_options_test.rb"]
|
|
16
|
+
},
|
|
17
|
+
"delivery-gap" => {
|
|
18
|
+
implementation: "faulty/lib/delivery_options.rb",
|
|
19
|
+
specification: "specification.rb",
|
|
20
|
+
tests: [
|
|
21
|
+
"faulty/test/initial_delivery_options_test.rb",
|
|
22
|
+
"faulty/test/gap_delivery_options_test.rb"
|
|
23
|
+
]
|
|
24
|
+
},
|
|
25
|
+
"delivery-corrected" => {
|
|
26
|
+
implementation: "corrected/lib/delivery_options.rb",
|
|
27
|
+
specification: "specification.rb",
|
|
28
|
+
tests: ["corrected/test/delivery_options_test.rb"]
|
|
29
|
+
},
|
|
30
|
+
"booleans-baseline" => {
|
|
31
|
+
implementation: "pairwise_suggestions/three_boolean/lib/suggested_boolean_batch.rb",
|
|
32
|
+
specification: "pairwise_suggestions/three_boolean/specification.rb",
|
|
33
|
+
tests: ["pairwise_suggestions/three_boolean/test/baseline_test.rb"]
|
|
34
|
+
},
|
|
35
|
+
"booleans-complete" => {
|
|
36
|
+
implementation: "pairwise_suggestions/three_boolean/lib/suggested_boolean_batch.rb",
|
|
37
|
+
specification: "pairwise_suggestions/three_boolean/specification.rb",
|
|
38
|
+
tests: ["pairwise_suggestions/three_boolean/test/complete_test.rb"]
|
|
39
|
+
}
|
|
40
|
+
}.freeze
|
|
41
|
+
|
|
42
|
+
abort "Choose one of: #{scenarios.keys.join(", ")}" unless scenarios.key?(mode)
|
|
43
|
+
|
|
44
|
+
scenario = scenarios.fetch(mode)
|
|
45
|
+
root = mode.start_with?("delivery-") ? pairwise_root : File.expand_path("..", __dir__)
|
|
46
|
+
require File.join(root, scenario.fetch(:implementation))
|
|
47
|
+
|
|
48
|
+
SpecProof::Minitest.install!(
|
|
49
|
+
specifications: [File.join(root, scenario.fetch(:specification))],
|
|
50
|
+
criterion: "pairwise-finite-v1",
|
|
51
|
+
output: output,
|
|
52
|
+
fail_on_missing: false
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
scenario.fetch(:tests).each { |path| require File.join(root, path) }
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# Boolean batch requirements
|
|
2
|
+
|
|
3
|
+
`SuggestedBooleanBatch#build` returns the supplied three boolean values in
|
|
4
|
+
`[first, second, third]` order. Every triple is permitted. `context` is an
|
|
5
|
+
application value outside this finite pairwise scenario; callers must supply a
|
|
6
|
+
value appropriate for their application. The pairwise suggestions specify
|
|
7
|
+
neither that value nor an expected return value.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "specproof"
|
|
4
|
+
|
|
5
|
+
SpecProof.define "SuggestedBooleanBatch#build" do
|
|
6
|
+
input :first, values: [false, true]
|
|
7
|
+
input :second, values: [false, true]
|
|
8
|
+
input :third, values: [false, true]
|
|
9
|
+
exclude_input :context, reason: "Context behavior is outside this boolean interaction example"
|
|
10
|
+
combinations :first, :second, :third, all_allowed: true
|
|
11
|
+
end
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Verifies the single tuple observed before pairwise suggestions.
|
|
4
|
+
class SuggestedBooleanBatchBaselineTest < Minitest::Test
|
|
5
|
+
def test_all_false_returns_the_input_values_in_order
|
|
6
|
+
assert_equal([false, false, false],
|
|
7
|
+
SuggestedBooleanBatch.new.build(first: false, second: false, third: false, context: Object.new))
|
|
8
|
+
end
|
|
9
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Checks a compact covering set against the independent array requirement.
|
|
4
|
+
class SuggestedBooleanBatchCompleteTest < Minitest::Test
|
|
5
|
+
def setup
|
|
6
|
+
@batch = SuggestedBooleanBatch.new
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def test_each_requirement_backed_assignment_returns_values_in_order
|
|
10
|
+
[
|
|
11
|
+
[false, false, false],
|
|
12
|
+
[false, true, true],
|
|
13
|
+
[true, false, true],
|
|
14
|
+
[true, true, false]
|
|
15
|
+
].each do |values|
|
|
16
|
+
first, second, third = values
|
|
17
|
+
assert_equal(values,
|
|
18
|
+
@batch.build(first: first, second: second, third: third, context: Object.new),
|
|
19
|
+
"Requirement: preserve boolean input order; see requirements.md")
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# RBS-backed registration example
|
|
2
|
+
|
|
3
|
+
This example imports `age: Integer` and optional nullable `consent: bool?`
|
|
4
|
+
from [the explicit RBS signature](sig/registration.rbs), then uses the DSL
|
|
5
|
+
only to refine age to `0..120` with complete, non-overlapping minor/adult
|
|
6
|
+
partitions. The independent rule is in [requirements.md](requirements.md): an
|
|
7
|
+
applicant is eligible exactly when age is at least 18 and consent is true.
|
|
8
|
+
Missing consent and explicit false both deny eligibility.
|
|
9
|
+
|
|
10
|
+
The composed plan has nine marginal obligations: six age candidates
|
|
11
|
+
(`0`, `1`, `17`, `18`, `119`, `120`) and three consent values (`nil`, `false`,
|
|
12
|
+
`true`). The nine obligations do not mean nine combinations and do not state
|
|
13
|
+
any expected return value.
|
|
14
|
+
|
|
15
|
+
Run all commands from the gem directory. The RBS parser is in the explicit
|
|
16
|
+
optional bundle. Every command starts a fresh Ruby process. Offline check and
|
|
17
|
+
plan emit JSON to stdout; the three runtime checkpoints below write separate
|
|
18
|
+
coverage reports under `/private/tmp`:
|
|
19
|
+
|
|
20
|
+
```sh
|
|
21
|
+
cd gems/specproof
|
|
22
|
+
BUNDLE_GEMFILE=Gemfile.rbs bundle exec ruby -Ilib exe/specproof check examples/rbs_registration/specification.rb --rbs examples/rbs_registration/sig/registration.rbs --target 'Registration#eligible?' --format json
|
|
23
|
+
BUNDLE_GEMFILE=Gemfile.rbs bundle exec ruby -Ilib exe/specproof plan examples/rbs_registration/specification.rb --rbs examples/rbs_registration/sig/registration.rbs --target 'Registration#eligible?' --format json
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
The baseline implementation uses `age > 18`. It tests every declared age
|
|
27
|
+
candidate except 18, across each consent value. Its meaningful assertions
|
|
28
|
+
follow the written eligibility rule, and its fresh report should show eight of
|
|
29
|
+
nine marginal obligations exercised, with only the age-18 obligation missing:
|
|
30
|
+
|
|
31
|
+
```sh
|
|
32
|
+
SPEC_PROOF_REPORT=/private/tmp/registration-baseline.json CHECKPOINT=baseline BUNDLE_GEMFILE=Gemfile.rbs bundle exec ruby -I examples/rbs_registration/implementations/age_gt examples/rbs_registration/test_registration.rb
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Run the added age-18 assertion alongside the passing baseline cases against
|
|
36
|
+
that same faulty implementation. The assertion should fail because an
|
|
37
|
+
18-year-old who consents is eligible under the stated rule. The report should
|
|
38
|
+
show all nine candidates hit but `tests_passed: false`, demonstrating that
|
|
39
|
+
input coverage alone does not establish correctness:
|
|
40
|
+
|
|
41
|
+
```sh
|
|
42
|
+
SPEC_PROOF_REPORT=/private/tmp/registration-boundary-red.json CHECKPOINT=boundary BUNDLE_GEMFILE=Gemfile.rbs bundle exec ruby -I examples/rbs_registration/implementations/age_gt examples/rbs_registration/test_registration.rb
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
The final checkpoint loads the separate corrected implementation (`age >= 18`)
|
|
46
|
+
and tests all six age candidates with all three consent values. Its new report
|
|
47
|
+
should have a distinct run ID and all nine unchanged obligations exercised:
|
|
48
|
+
|
|
49
|
+
```sh
|
|
50
|
+
SPEC_PROOF_REPORT=/private/tmp/registration-corrected.json CHECKPOINT=complete BUNDLE_GEMFILE=Gemfile.rbs bundle exec ruby -I examples/rbs_registration/implementations/age_gte examples/rbs_registration/test_registration.rb
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Inspect each fresh report's digest, obligation IDs, run ID, completion,
|
|
54
|
+
integrity, `tests_passed`, and declaration scope. The digest covers normalized
|
|
55
|
+
input domains and partition policy; it does not fingerprint all source text,
|
|
56
|
+
the full signature, or implementation correctness. A passing input-coverage
|
|
57
|
+
report is evidence that candidates reached the method, while the behavior
|
|
58
|
+
assertions and their independent rule establish expected results.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# Registration eligibility rule
|
|
2
|
+
|
|
3
|
+
An applicant is eligible exactly when they are at least 18 years old and have
|
|
4
|
+
given consent. Applicants younger than 18 are ineligible. Missing consent and
|
|
5
|
+
explicitly false consent both mean the applicant has not given consent and are
|
|
6
|
+
ineligible. An 18-year-old who consents is eligible.
|
|
7
|
+
|
|
8
|
+
This is the expected-result rule for the example tests. The RBS signature only
|
|
9
|
+
describes accepted input types; it does not provide this behavior rule.
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "minitest/autorun"
|
|
4
|
+
require "registration"
|
|
5
|
+
require "specproof/minitest"
|
|
6
|
+
|
|
7
|
+
REPORT = ENV.fetch("SPEC_PROOF_REPORT", "/private/tmp/specproof-registration-#{Process.pid}.json")
|
|
8
|
+
RBS_PATH = File.join(__dir__, "sig/registration.rbs")
|
|
9
|
+
SPECIFICATION = File.join(__dir__, "specification.rb")
|
|
10
|
+
|
|
11
|
+
SpecProof::Minitest.install!(
|
|
12
|
+
specifications: [SPECIFICATION],
|
|
13
|
+
rbs: [RBS_PATH],
|
|
14
|
+
targets: ["Registration#eligible?"],
|
|
15
|
+
output: REPORT,
|
|
16
|
+
fail_on_missing: false
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
# Exercises only explicit RBS-backed input obligations for the example method.
|
|
20
|
+
class RegistrationExampleTest < Minitest::Test
|
|
21
|
+
CONSENT_VALUES = [nil, false, true].freeze
|
|
22
|
+
COMPLETE_AGES = [0, 1, 17, 18, 119, 120].freeze
|
|
23
|
+
GAP_AGES = [0, 1, 17, 119, 120].freeze
|
|
24
|
+
|
|
25
|
+
if ENV["CHECKPOINT"] == "boundary"
|
|
26
|
+
def test_age_18_with_consent_is_eligible
|
|
27
|
+
assert Registration.new.eligible?(age: 18, consent: true),
|
|
28
|
+
"requirements.md: an 18-year-old who consents is eligible"
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def test_declared_age_and_consent_candidates_follow_the_eligibility_rule
|
|
33
|
+
ages = %w[baseline boundary].include?(ENV["CHECKPOINT"]) ? GAP_AGES : COMPLETE_AGES
|
|
34
|
+
|
|
35
|
+
ages.product(CONSENT_VALUES).each do |age, consent|
|
|
36
|
+
expected = age >= 18 && consent == true
|
|
37
|
+
message = "requirements.md: eligible iff age >= 18 and consent is true " \
|
|
38
|
+
"(age=#{age}, consent=#{consent.inspect})"
|
|
39
|
+
assert_equal expected, Registration.new.eligible?(age: age, consent: consent),
|
|
40
|
+
message
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# RBS finite string literal example
|
|
2
|
+
|
|
3
|
+
This example imports `country: ("ro" | "de")` and `expedited: bool` from the
|
|
4
|
+
explicit RBS signature. The DSL declares only that every combination is
|
|
5
|
+
allowed; it does not duplicate either input. The independent
|
|
6
|
+
[requirement](requirements.md) says `build` preserves the requested country and
|
|
7
|
+
uses `express` exactly when expedited is true.
|
|
8
|
+
|
|
9
|
+
Run from the gem directory. The RBS parser is in the optional `Gemfile.rbs`
|
|
10
|
+
bundle. Each command starts a fresh Ruby process and writes its report under
|
|
11
|
+
`tmp/`:
|
|
12
|
+
|
|
13
|
+
```sh
|
|
14
|
+
BUNDLE_GEMFILE=Gemfile.rbs bundle exec ruby -Ilib exe/specproof plan examples/rbs_string_literals/specification.rb --rbs examples/rbs_string_literals/sig/delivery_options.rbs --target 'DeliveryOptions#build' --criterion pairwise-finite-v1 --format json
|
|
15
|
+
BUNDLE_GEMFILE=Gemfile.rbs bundle exec ruby examples/rbs_string_literals/test_delivery_options.rb
|
|
16
|
+
BUNDLE_GEMFILE=Gemfile.rbs bundle exec ruby -Ilib exe/specproof suggest examples/rbs_string_literals/specification.rb --rbs examples/rbs_string_literals/sig/delivery_options.rbs --target 'DeliveryOptions#build' --report tmp/rbs-string-baseline.json --format json
|
|
17
|
+
SPEC_PROOF_REPORT=tmp/rbs-string-complete.json CHECK_GAP=1 BUNDLE_GEMFILE=Gemfile.rbs bundle exec ruby examples/rbs_string_literals/test_delivery_options.rb
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
The first coverage run passes three requirement-backed calls: `ro` with each
|
|
21
|
+
boolean value and `de` with false. Its fresh report leaves the pair
|
|
22
|
+
`country: "de", expedited: true` unobserved. `suggest` proposes that allowed
|
|
23
|
+
assignment; the suggestion supplies an input only, with no expected result.
|
|
24
|
+
The final fresh run adds a separate assertion justified by the requirement and
|
|
25
|
+
exercises the missing assignment. The example uses the correct implementation
|
|
26
|
+
throughout; it demonstrates a coverage gap without manufacturing a behavior
|
|
27
|
+
defect. Inspect both reports' run IDs, digests, completion, integrity, test
|
|
28
|
+
results, and input scope.
|
|
29
|
+
|
|
30
|
+
RBS `String` is unrestricted and remains unsupported. A selected union with an
|
|
31
|
+
unsupported member also fails atomically. Return types do not provide an
|
|
32
|
+
expected-result oracle. Finite-string support is feature-specific, not
|
|
33
|
+
guaranteed by a `0.1.x` version number; confirm typed string obligations in a
|
|
34
|
+
fresh plan from the installed build.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# Delivery options requirement
|
|
2
|
+
|
|
3
|
+
For the supported country values `"ro"` and `"de"`,
|
|
4
|
+
`DeliveryOptions#build(country:, expedited:)` preserves the supplied country
|
|
5
|
+
string in its result. It selects `"express"` when `expedited` is true and
|
|
6
|
+
`"standard"` when it is false. The country and expedited choice are independent
|
|
7
|
+
options, so every combination is allowed.
|
|
8
|
+
|
|
9
|
+
This requirement is the expected-result source for the example's assertions.
|
|
10
|
+
The RBS signature describes accepted input values only; its return type is not
|
|
11
|
+
used as a behavior oracle.
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "minitest/autorun"
|
|
4
|
+
require_relative "lib/delivery_options"
|
|
5
|
+
require "specproof/minitest"
|
|
6
|
+
|
|
7
|
+
ROOT = File.expand_path("../..", __dir__)
|
|
8
|
+
SPECIFICATION = File.join(__dir__, "specification.rb")
|
|
9
|
+
RBS_SIGNATURE = File.join(__dir__, "sig/delivery_options.rbs")
|
|
10
|
+
REPORT = ENV.fetch("SPEC_PROOF_REPORT", File.join(ROOT, "tmp/rbs-string-baseline.json"))
|
|
11
|
+
|
|
12
|
+
SpecProof::Minitest.install!(
|
|
13
|
+
specifications: [SPECIFICATION],
|
|
14
|
+
rbs: [RBS_SIGNATURE],
|
|
15
|
+
targets: ["DeliveryOptions#build"],
|
|
16
|
+
criterion: "pairwise-finite-v1",
|
|
17
|
+
output: REPORT,
|
|
18
|
+
fail_on_missing: false
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
# Exercises the RBS-backed delivery options contract.
|
|
22
|
+
class DeliveryOptionsExampleTest < Minitest::Test
|
|
23
|
+
BASELINE_ASSIGNMENTS = [
|
|
24
|
+
["ro", false],
|
|
25
|
+
["ro", true],
|
|
26
|
+
["de", false]
|
|
27
|
+
].freeze
|
|
28
|
+
|
|
29
|
+
def test_baseline_assignments_follow_delivery_requirement
|
|
30
|
+
delivery = DeliveryOptions.new
|
|
31
|
+
|
|
32
|
+
BASELINE_ASSIGNMENTS.each do |country, expedited|
|
|
33
|
+
expected_shipping = expedited ? "express" : "standard"
|
|
34
|
+
expected = "#{country}:#{expected_shipping}"
|
|
35
|
+
assert_equal expected, delivery.build(country: country, expedited: expedited),
|
|
36
|
+
"requirements.md: preserve country and select shipping by expedited"
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
if ENV["CHECK_GAP"] == "1"
|
|
41
|
+
def test_suggested_de_expedited_assignment_follows_requirement
|
|
42
|
+
assert_equal "de:express", DeliveryOptions.new.build(country: "de", expedited: true),
|
|
43
|
+
"requirements.md: preserve de and select express when expedited"
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
SpecProof.define "Shipping#available?" do
|
|
4
|
+
input :country, values: %i[ca gb us], allow_gaps: false, allow_overlaps: false do
|
|
5
|
+
partition :supported, values: %i[ca gb us]
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
input :expedited, values: [false, true] do
|
|
9
|
+
partition :standard, values: [false]
|
|
10
|
+
partition :express, values: [true]
|
|
11
|
+
end
|
|
12
|
+
end
|