Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
241 changes: 241 additions & 0 deletions features/basics/pretty-print.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,241 @@
Feature: Pretty printing Contract violations

Scenario: Big array argument being passed to big array method parameter
Given a file named "example.rb" with:
"""ruby
require "contracts"
C = Contracts

class Example
include Contracts::Core

class << self
Contract [
C::Or[String, Symbol],
C::Or[String, Symbol],
C::Or[String, Symbol],
C::Or[String, Symbol],
C::Or[String, Symbol],
C::Or[String, Symbol],
C::Or[String, Symbol]
] => nil
def run(data)
nil
end
end
end

puts Example.run([
["foo", "foo"],
["foo", "foo"],
["foo", "foo"],
["foo", "foo"],
["foo", "foo"],
["foo", "foo"],
["foo", "foo"],
["foo", "foo"],
["foo", "foo"]
])
"""
When I run `ruby example.rb`
Then the output should contain:
"""
: Contract violation for argument 1 of 1: (ParamContractError)
Expected: [(String or Symbol),
(String or Symbol),
(String or Symbol),
(String or Symbol),
(String or Symbol),
(String or Symbol),
(String or Symbol)],
Actual: [["foo", "foo"],
["foo", "foo"],
["foo", "foo"],
["foo", "foo"],
["foo", "foo"],
["foo", "foo"],
["foo", "foo"],
["foo", "foo"],
["foo", "foo"]]
Value guarded in: Example::run
With Contract: Array => NilClass
At: example.rb:17
"""

Scenario: Big array value being returned from method expecting different big array type
Given a file named "example.rb" with:
"""ruby
require "contracts"
C = Contracts

class Example
include Contracts::Core

class << self
Contract C::None => [
C::Or[String, Symbol],
C::Or[String, Symbol],
C::Or[String, Symbol],
C::Or[String, Symbol],
C::Or[String, Symbol],
C::Or[String, Symbol],
C::Or[String, Symbol]
]
def run
[
["foo", "foo"],
["foo", "foo"],
["foo", "foo"],
["foo", "foo"],
["foo", "foo"],
["foo", "foo"],
["foo", "foo"],
["foo", "foo"],
["foo", "foo"]
]
end
end
end

puts Example.run
"""
When I run `ruby example.rb`
Then the output should contain:
"""
: Contract violation for return value: (ReturnContractError)
Expected: [(String or Symbol),
(String or Symbol),
(String or Symbol),
(String or Symbol),
(String or Symbol),
(String or Symbol),
(String or Symbol)],
Actual: [["foo", "foo"],
["foo", "foo"],
["foo", "foo"],
["foo", "foo"],
["foo", "foo"],
["foo", "foo"],
["foo", "foo"],
["foo", "foo"],
["foo", "foo"]]
Value guarded in: Example::run
With Contract: None => Array
At: example.rb:17
"""

Scenario: Big hash argument being passed to big hash method parameter
Given a file named "example.rb" with:
"""ruby
require "contracts"
C = Contracts

class Example
include Contracts::Core

class << self
Contract ({
a: C::Or[String, Symbol],
b: C::Or[String, Symbol],
c: C::Or[String, Symbol],
d: C::Or[String, Symbol],
e: C::Or[String, Symbol],
f: C::Or[String, Symbol],
g: C::Or[String, Symbol]
}) => nil
def run(data)
nil
end
end
end

puts Example.run({
a: ["foo", "foo"],
b: ["foo", "foo"],
c: ["foo", "foo"],
d: ["foo", "foo"],
e: ["foo", "foo"],
f: ["foo", "foo"],
g: ["foo", "foo"]
})
"""
When I run `ruby example.rb`
Then the output should contain:
"""
: Contract violation for argument 1 of 1: (ParamContractError)
Expected: {:a=>(String or Symbol),
:b=>(String or Symbol),
:c=>(String or Symbol),
:d=>(String or Symbol),
:e=>(String or Symbol),
:f=>(String or Symbol),
:g=>(String or Symbol)},
Actual: {:a=>["foo", "foo"],
:b=>["foo", "foo"],
:c=>["foo", "foo"],
:d=>["foo", "foo"],
:e=>["foo", "foo"],
:f=>["foo", "foo"],
:g=>["foo", "foo"]}
Value guarded in: Example::run
With Contract: Hash => NilClass
At: example.rb:17
"""

Scenario: Big hash value being returned from method expecting different big hash type
Given a file named "example.rb" with:
"""ruby
require "contracts"
C = Contracts

class Example
include Contracts::Core

class << self
Contract C::None => ({
a: C::Or[String, Symbol],
b: C::Or[String, Symbol],
c: C::Or[String, Symbol],
d: C::Or[String, Symbol],
e: C::Or[String, Symbol],
f: C::Or[String, Symbol],
g: C::Or[String, Symbol]
})
def run
{
a: ["foo", "foo"],
b: ["foo", "foo"],
c: ["foo", "foo"],
d: ["foo", "foo"],
e: ["foo", "foo"],
f: ["foo", "foo"],
g: ["foo", "foo"]
}
end
end
end

puts Example.run
"""
When I run `ruby example.rb`
Then the output should contain:
"""
: Contract violation for return value: (ReturnContractError)
Expected: {:a=>(String or Symbol),
:b=>(String or Symbol),
:c=>(String or Symbol),
:d=>(String or Symbol),
:e=>(String or Symbol),
:f=>(String or Symbol),
:g=>(String or Symbol)},
Actual: {:a=>["foo", "foo"],
:b=>["foo", "foo"],
:c=>["foo", "foo"],
:d=>["foo", "foo"],
:e=>["foo", "foo"],
:f=>["foo", "foo"],
:g=>["foo", "foo"]}
Value guarded in: Example::run
With Contract: None => Hash
At: example.rb:17
"""
51 changes: 43 additions & 8 deletions lib/contracts.rb
Original file line number Diff line number Diff line change
Expand Up @@ -116,22 +116,57 @@ def to_s
# This function is used by the default #failure_callback method
# and uses the hash passed into the failure_callback method.
def self.failure_msg(data)
expected = Contracts::Formatters::Expected.new(data[:contract]).contract
position = Contracts::Support.method_position(data[:method])
indent_amount = 8
method_name = Contracts::Support.method_name(data[:method])

# Header
header = if data[:return_value]
"Contract violation for return value:"
else
"Contract violation for argument #{data[:arg_pos]} of #{data[:total_args]}:"
end

%{#{header}
Expected: #{expected},
Actual: #{data[:arg].inspect}
Value guarded in: #{data[:class]}::#{method_name}
With Contract: #{data[:contracts]}
At: #{position} }
# Expected
expected_prefix = "Expected: "
expected_value = Contracts::Support.indent_string(
Contracts::Formatters::Expected.new(data[:contract]).contract.pretty_inspect,
expected_prefix.length
).strip
expected_line = expected_prefix + expected_value + ","

# Actual
actual_prefix = "Actual: "
actual_value = Contracts::Support.indent_string(
data[:arg].pretty_inspect,
actual_prefix.length
).strip
actual_line = actual_prefix + actual_value

# Value guarded in
value_prefix = "Value guarded in: "
value_value = "#{data[:class]}::#{method_name}"
value_line = value_prefix + value_value

# Contract
contract_prefix = "With Contract: "
contract_value = data[:contracts].to_s
contract_line = contract_prefix + contract_value

# Position
position_prefix = "At: "
position_value = Contracts::Support.method_position(data[:method])
position_line = position_prefix + position_value

header +
"\n" +
Contracts::Support.indent_string(
[expected_line,
actual_line,
value_line,
contract_line,
position_line].join("\n"),
indent_amount
)
end

# Callback for when a contract fails. By default it raises
Expand Down
6 changes: 4 additions & 2 deletions lib/contracts/formatters.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
require "pp"

module Contracts
# A namespace for classes related to formatting.
module Formatters
Expand Down Expand Up @@ -25,13 +27,13 @@ def hash_contract(hash)
@full = true # Complex values output completely, overriding @full
hash.inject({}) do |repr, (k, v)|
repr.merge(k => InspectWrapper.create(contract(v), @full))
end.inspect
end
end

# Formats Array contracts.
def array_contract(array)
@full = true
array.map { |v| InspectWrapper.create(contract(v), @full) }.inspect
array.map { |v| InspectWrapper.create(contract(v), @full) }
end
end

Expand Down
7 changes: 7 additions & 0 deletions lib/contracts/support.rb
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,13 @@ def eigenclass?(target)
target <= eigenclass_of(Object)
end

def indent_string(string, amount)
string.gsub(
/^(?!$)/,
(string[/^[ \t]/] || " ") * amount
)
end

private

# Module eigenclass can be detected by its ancestor chain
Expand Down
22 changes: 22 additions & 0 deletions spec/contracts_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -637,6 +637,28 @@ def delim(match)
end.to raise_error(ContractError, not_s(delim "String or Symbol"))
end

it "should wrap and pretty print for long param contracts" do
expect do
@o.long_array_param_contracts(true)
end.to(
raise_error(
ParamContractError,
/\[\(String or Symbol\),\n \(String or Symbol\),/
)
)
end

it "should wrap and pretty print for long return contracts" do
expect do
@o.long_array_return_contracts
end.to(
raise_error(
ReturnContractError,
/\[\(String or Symbol\),\n \(String or Symbol\),/
)
)
end

it "should not contain Contracts:: module prefix" do
expect do
@o.double("bad")
Expand Down
Loading