Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Quality of a properly implemented Equals Contract, closes #16 #93

Merged
merged 1 commit into from
Aug 20, 2023
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
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ private static <T> Assessment resolve(
{
for (int j = 0; j < qualities.size(); j++)
{
if (asses(candidates.get(i), qualities.get((j)), cache))
if (assess(candidates.get(i), qualities.get((j)), cache))
{
List<T> can2 = new ArrayList<>(candidates);
can2.remove(i);
Expand All @@ -165,7 +165,7 @@ private static <T> Assessment resolve(


@SuppressWarnings("NewApi")
private static <T> Boolean asses(T candidate, Quality<? super T> quality, Map<T, Map<Quality<? super T>, Boolean>> cache)
private static <T> Boolean assess(T candidate, Quality<? super T> quality, Map<T, Map<Quality<? super T>, Boolean>> cache)
{
return cache.computeIfAbsent(candidate, i -> new HashMap<>())
.computeIfAbsent(quality, i -> quality.assessmentOf(candidate).isSuccess());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@

import org.dmfs.jems2.iterable.Mapped;
import org.dmfs.srcless.annotations.staticfactory.StaticFactories;
import org.saynotobugs.confidence.Assessment;
import org.saynotobugs.confidence.Quality;
import org.saynotobugs.confidence.assessment.FailPrepended;
import org.saynotobugs.confidence.assessment.PassIf;
Expand All @@ -39,16 +38,15 @@ public final class EqualTo<T> extends QualityComposition<T>
*/
public EqualTo(T expected)
{
super(actual -> asses(expected, actual),
super(actual -> expected.getClass().isArray() && actual.getClass().isArray()
?
new FailPrepended(
new Text("array that"),
new Iterates<>(new Mapped<>(EqualTo::new, new ArrayIterable(expected))).assessmentOf(new ArrayIterable(actual)))
:
new PassIf(expected.equals(actual), new Value(actual)),
new Value(expected));
}


private static <T> Assessment asses(T expected, T actual)
{
return expected.getClass().isArray() && actual.getClass().isArray()
? new FailPrepended(new Text("array that"),
new Iterates<>(new Mapped<>(EqualTo::new, new ArrayIterable(expected))).assessmentOf(new ArrayIterable(actual)))
: new PassIf(expected.equals(actual), new Value(actual));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
* Copyright 2023 dmfs GmbH
*
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/

package org.saynotobugs.confidence.quality.object;

import org.saynotobugs.confidence.Description;
import org.saynotobugs.confidence.description.Spaced;
import org.saynotobugs.confidence.description.Text;
import org.saynotobugs.confidence.description.Value;
import org.saynotobugs.confidence.quality.composite.Has;
import org.saynotobugs.confidence.quality.composite.QualityComposition;

public final class HashCodeEquals extends QualityComposition<Object>
{

public HashCodeEquals(int hashCode)
{
super(new Has<>("hashCode", Object::hashCode, new EqualTo<>(hashCode)));
}

public HashCodeEquals(Object referenceObject)
{
super(new Has<>((Description orig) -> new Spaced(new Text("has hashCode"), orig, new Text("like"), new Value(referenceObject)),
orig -> new Spaced(new Text("had hashCode"), orig),
Object::hashCode, new EqualTo<>(referenceObject.hashCode())));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*
* Copyright 2022 dmfs GmbH
*
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/

package org.saynotobugs.confidence.quality.object;

import org.dmfs.srcless.annotations.staticfactory.StaticFactories;
import org.saynotobugs.confidence.Quality;
import org.saynotobugs.confidence.description.Spaced;
import org.saynotobugs.confidence.description.Text;
import org.saynotobugs.confidence.description.Value;
import org.saynotobugs.confidence.quality.composite.AllOf;
import org.saynotobugs.confidence.quality.composite.QualityComposition;
import org.saynotobugs.confidence.quality.grammar.Is;


@StaticFactories(value = "Core", packageName = "org.saynotobugs.confidence.quality")
public final class StrictlyEqualTo<T> extends QualityComposition<T>
{
/**
* A {@link Quality} that is satisfied if the value under test is strictly equal to the given value.
* <p>
* "Strictly equal" means the object satisfies the equals contract as described in {@link Object#equals(Object)}.
* This is intended to test implementation of the equals contract, not for general testing for equality and may result
* in unexpected outcomes, especially when used with negation.
* <p>
* Note, this {@link Quality} does not support arrays.
*/
public StrictlyEqualTo(T expected)
{
super(new AllOf<>(
new Is<>(new Satisfies<>(actual -> actual.equals(actual), any -> new Text("not reflexive"), new Text("reflexive"))),
new Is<>(new Satisfies<>(actual -> !actual.equals(null), any -> new Text("equal to null"), new Text("not equal to null"))),
new Is<>(new Satisfies<>(expected::equals, any -> new Spaced(new Text("not equal to"), new Value(expected)), new Spaced(new Text("equal to"), new Value(expected)))),
new Is<>(new Satisfies<>(actual -> actual.equals(expected), any -> new Text("not symmetric"), new Text("symmetric"))),
new HashCodeEquals(expected)));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*
* Copyright 2023 dmfs GmbH
*
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/

package org.saynotobugs.confidence.quality.object;

import org.junit.jupiter.api.Test;
import org.saynotobugs.confidence.quality.composite.AllOf;
import org.saynotobugs.confidence.test.quality.Fails;
import org.saynotobugs.confidence.test.quality.HasDescription;
import org.saynotobugs.confidence.test.quality.Passes;

import static org.saynotobugs.confidence.Assertion.assertThat;

class HashCodeEqualsTest
{
@Test
void testHashCode()
{
assertThat(new HashCodeEquals(123),
new AllOf<>(
new Passes<>(123, 123L),
new Fails<>(124, "had hashCode <124>"),
new HasDescription("has hashCode <123>")
));
}

@Test
void testReferenceObject()
{
assertThat(new HashCodeEquals("123"),
new AllOf<>(
new Passes<>("123"),
new Fails<Object>(321,"had hashCode <321>"),
new HasDescription("has hashCode <48690> like \"123\"")
));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/*
* Copyright 2023 dmfs GmbH
*
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/

package org.saynotobugs.confidence.quality.object;

import org.junit.jupiter.api.Test;
import org.saynotobugs.confidence.test.quality.Fails;
import org.saynotobugs.confidence.test.quality.HasDescription;
import org.saynotobugs.confidence.test.quality.Passes;

import static org.saynotobugs.confidence.Assertion.assertThat;
import static org.saynotobugs.confidence.quality.Core.allOf;

class StrictlyEqualToTest
{
@Test
void testSingle()
{
assertThat(new StrictlyEqualTo<>("123"),
allOf(
new Passes<>("123"),
new Fails<Object>("456", "{ ...\n was not equal to \"123\"\n and\n was not symmetric\n and\n had hashCode <51669> }"),
new Fails<>(new Object()
{
@Override
public boolean equals(Object obj)
{
return "123".equals(obj);
}

@Override
public int hashCode()
{
return "123".hashCode();
}

@Override
public String toString()
{
return "fakeObject1";
}
}, "{ was not reflexive\n ...\n was not equal to \"123\"\n ... }"),
new Fails<>(new Object()
{
@Override
public boolean equals(Object obj)
{
return "123".equals(obj) || obj == null;
}

@Override
public int hashCode()
{
return "456".hashCode();
}

@Override
public String toString()
{
return "fakeObject2";
}
}, "{ was not reflexive\n and\n was equal to null\n and\n was not equal to \"123\"\n ...\n had hashCode <51669> }"),
new Fails<>(new Object()
{
@Override
public boolean equals(Object obj)
{
return "123".equals(obj) || obj == this;
}

@Override
public int hashCode()
{
return "123".hashCode();
}

@Override
public String toString()
{
return "fakeObject3";
}
}, "{ ...\n was not equal to \"123\"\n ... }"),
new HasDescription("is reflexive\n and\n is not equal to null\n and\n is equal to \"123\"\n and\n is symmetric\n and\n has hashCode <48690> like \"123\"")
));
}
}