Make Table.should_equal and Column.should_equal consider NaN equal (#9799)

* Make Column.should_equal detect colums of different types and think nan==nan

* Refactor Table.should_equal

* More Column tests

* Adjust spacing

* Tests Green

* Check same number of columns

* Refactor

* Extra test

* Code Review Changes

* Fix

* Fix

* Fix tests

* Fix Tests

* Fix Test

* Fix test

* Code review change
This commit is contained in:
AdRiley 2024-04-30 00:21:34 +03:00 committed by GitHub
parent 04a10b72e5
commit 32c3f5f3e8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 149 additions and 23 deletions

View File

@ -6,6 +6,7 @@ import java.time.LocalTime;
import java.time.ZonedDateTime;
import java.util.List;
import org.enso.base.polyglot.NumericConverter;
import org.enso.base.polyglot.Polyglot_Utils;
import org.enso.table.data.column.storage.Storage;
import org.enso.table.data.column.storage.type.BigIntegerType;
import org.enso.table.data.column.storage.type.BooleanType;
@ -61,6 +62,9 @@ public class InferredBuilder extends Builder {
@Override
public void append(Object o) {
// ToDo: This a workaround for an issue with polyglot layer. #5590 is related.
o = Polyglot_Utils.convertPolyglotValue(o);
if (currentBuilder == null) {
if (o == null) {
currentSize++;

View File

@ -298,7 +298,7 @@ add_specs suite_builder =
suite_builder.group "Edge cases" group_builder->
group_builder.specify "empty table is ok" <|
input = Column.from_vector "values" [Date.new 2020 12 21, Date.new 2023 4 25] . take 0
expected = Column.from_vector "values" []
expected = Column.from_vector "values" [""] . take 0
actual = input.format "yyyyMMdd"
actual . should_equal expected

View File

@ -351,7 +351,7 @@ add_specs suite_builder =
group_builder.specify "empty table" <|
t = Table.from_rows ["foo", "bar", "baz"] [["x", "a", "y"]] . take 0
expected = Table.from_rows ["foo", "bar", "baz"] []
expected = Table.from_rows ["foo", "bar", "baz"] [["x", "a", "y"]] . take 0
actual = t.parse_to_columns "bar" "\d+"
actual.should_equal expected
@ -380,13 +380,13 @@ add_specs suite_builder =
group_builder.specify "input with no matches, with regex groups" <|
t = Table.from_rows ["foo", "bar", "baz"] [["x", "a", "y"]]
expected = Table.from_rows ["foo", "bar 1", "bar 2", "baz"] [["x", Nothing, Nothing, "y"]]
expected = Table.from_rows ["foo", "bar 1", "bar 2", "baz"] [["x", Nothing, Nothing, "y"], ["", "", "", ""]] . take 1
actual = t.parse_to_columns "bar" "(\d)(\d)"
actual.should_equal expected
group_builder.specify "input with no matches, with named and unnamed regex groups" <|
t = Table.from_rows ["foo", "bar", "baz"] [["x", "a", "y"]]
expected = Table.from_rows ["foo", "quux", "bar 1", "foo 1", "bar 2", "baz"] [["x", Nothing, Nothing, Nothing, Nothing, "y"]]
expected = Table.from_rows ["foo", "quux", "bar 1", "foo 1", "bar 2", "baz"] [["x", Nothing, Nothing, Nothing, Nothing, "y"], ["", "", "", "", "", ""]] . take 1
actual = t.parse_to_columns "bar" "(?<quux>)(\d)(?<foo>\d)(\d)"
actual.should_equal expected

View File

@ -7,6 +7,7 @@ import project.Formatting
import project.Helpers
import project.In_Memory
import project.IO
import project.Util_Spec
main filter=Nothing =
suite = Test.build suite_builder->
@ -15,5 +16,6 @@ main filter=Nothing =
Formatting.Main.add_specs suite_builder
Database.Main.add_specs suite_builder
Helpers.Main.add_specs suite_builder
Util_Spec.add_specs suite_builder
suite.run_with_filter filter

View File

@ -11,29 +11,14 @@ polyglot java import org.enso.base_test_helpers.FileSystemHelper
Table.should_equal : Any -> Integer -> Any
Table.should_equal self expected frames_to_skip=0 =
loc = Meta.get_source_location 1+frames_to_skip
case expected of
_ : Table ->
tables_equal t0 t1 =
same_headers = (t0.columns.map .name) == (t1.columns.map .name)
same_columns = (t0.columns.map .to_vector) == (t1.columns.map .to_vector)
same_headers && same_columns
equal = tables_equal self expected
if equal.not then
msg = 'Tables differ at '+loc+'.\nActual:\n' + self.display + '\nExpected:\n' + expected.display
Test.fail msg
_ -> Test.fail "Got a Table, but expected a "+expected.to_display_text+' (at '+loc+').'
Panic.catch Test_Failure_Error (table_should_equal_impl self expected loc) error->
Test.fail error.payload.message
Column.should_equal : Any -> Integer -> Any
Column.should_equal self expected frames_to_skip=0 =
loc = Meta.get_source_location 1+frames_to_skip
case expected of
_ : Column ->
if self.name != expected.name then
Test.fail "Expected column name "+expected.name+", but got "+self.name+" (at "+loc+")."
if self.length != expected.length then
Test.fail "Expected column length "+expected.length.to_text+", but got "+self.length.to_text+" (at "+loc+")."
self.to_vector.should_equal expected.to_vector 2+frames_to_skip
_ -> Test.fail "Got a Column, but expected a "+expected.to_display_text+' (at '+loc+').'
Panic.catch Test_Failure_Error (column_should_equal_impl self expected loc) error->
Test.fail error.payload.message
DB_Table.should_equal : DB_Table -> Integer -> Any
DB_Table.should_equal self expected frames_to_skip=0 =
@ -47,6 +32,60 @@ DB_Column.should_equal self expected frames_to_skip=0 =
t1 = expected.read
t0 . should_equal t1 frames_to_skip
type Test_Failure_Error
## PRIVATE
The runtime representation of a test failure.
Arguments:
- message: A description of the test failure.
Error message
## PRIVATE
to_display_text : Text
to_display_text self = "Test failure error: "+self.message
## PRIVATE
table_should_equal_impl actual expected loc =
case expected of
_ : Table ->
if actual.columns.length != expected.columns.length then
Panic.throw (Test_Failure_Error.Error 'Tables differ at '+loc+'.\nActual:\n'+actual.display+'\nExpected:\n'+expected.display+'\nExpected '+expected.columns.length.to_text+" columns, but got "+actual.columns.length.to_text+'.')
Panic.catch Test_Failure_Error (actual.columns.zip expected.columns a-> e->(column_should_equal_impl a e)) error->
msg = 'Tables differ at '+loc+'.\nActual:\n'+actual.display+'\nExpected:\n'+expected.display+'\n'+error.payload.message
Panic.throw (Test_Failure_Error.Error msg)
_ -> Panic.throw (Test_Failure_Error.Error "Got a Table, but expected a "+expected.to_display_text+(display_loc loc)+'.')
## PRIVATE
column_should_equal_impl actual expected loc='' =
case expected of
_ : Column ->
if actual.name != expected.name then
Panic.throw (Test_Failure_Error.Error "Expected column name "+expected.name+", but got "+actual.name+(display_loc loc)+'.')
if actual.length != expected.length then
Panic.throw (Test_Failure_Error.Error "Expected column length "+expected.length.to_text+", but got "+actual.length.to_text+(display_loc loc)+'.')
if actual.value_type != expected.value_type then
Panic.throw (Test_Failure_Error.Error "Expected column type "+expected.value_type.to_text+", but got "+actual.value_type.to_text+(display_loc loc)+'.')
actual.zip expected a-> e->
if values_equal a e then
report_fail actual expected loc
_ -> Panic.throw (Test_Failure_Error.Error "Got a Column, but expected a "+expected.to_display_text+(display_loc loc)+'.')
## PRIVATE
values_equal a e =
a != e && (a.is_a Number && e.is_a Number && a.is_nan && e.is_nan).not
## PRIVATE
report_fail actual expected loc =
indexed = actual.zip (0.up_to actual.length) a-> i-> Pair.new a i
indexed.zip expected a-> e->
if values_equal a.first e then
Panic.throw (Test_Failure_Error.Error "Column: "+actual.name+" differs at row "+a.second.to_text+'.\n\t Actual : '+a.first.to_text+'\n\t Expected: '+e.to_text+'\n\t'+(display_loc loc)+'.')
## PRIVATE
display_loc loc:Text =
if loc.is_empty then '' else
' (at '+loc+')'
normalize_lines string line_separator=Line_Ending_Style.Unix.to_text newline_at_end=True =
case newline_at_end of
True -> string.lines.join line_separator suffix=line_separator

View File

@ -0,0 +1,81 @@
from Standard.Base import all
from Standard.Table import Column, Table
from project.Util import all
from Standard.Test import all
add_specs suite_builder =
suite_builder.group "Column should_equal" group_builder->
group_builder.specify "Two Columns Are Equal" <|
expected_column = Column.from_vector "Col" ["Quis", "custodiet", "ipsos", "custodes?"]
actual_column = Column.from_vector "Col" ["Quis", "custodiet", "ipsos", "custodes?"]
actual_column.should_equal expected_column
group_builder.specify "Two Columns With Different Name are Not Equal" <|
expected_column = Column.from_vector "Col1" ["Quis", "custodiet", "ipsos", "custodes?"]
actual_column = Column.from_vector "Col2" ["Quis", "custodiet", "ipsos", "custodes?"]
res = Panic.recover Test_Failure_Error (column_should_equal_impl actual_column expected_column "LOCATION_PATH")
res.catch.message.should_equal "Expected column name Col1, but got Col2 (at LOCATION_PATH)."
group_builder.specify "Two Columns With Different Lengths are Not Equal" <|
expected_column = Column.from_vector "Col" ["Quis", "custodiet", "ipsos", "custodes?"]
actual_column = Column.from_vector "Col" ["Quis", "custodiet", "ipsos"]
res = Panic.recover Test_Failure_Error (column_should_equal_impl actual_column expected_column "LOCATION_PATH")
res.catch.message.should_equal "Expected column length 4, but got 3 (at LOCATION_PATH)."
group_builder.specify "Two Columns with different content Are Not Equal" <|
expected_column = Column.from_vector "Col" ["Quis", "custodiet", "ipsos", "custodes?"]
actual_column = Column.from_vector "Col" ["Who", "guards", "the", "guards?"]
res = Panic.recover Test_Failure_Error (column_should_equal_impl actual_column expected_column "LOCATION_PATH")
res.catch.message.should_equal 'Column: Col differs at row 0.\n\t Actual : Who\n\t Expected: Quis\n\t (at LOCATION_PATH).'
group_builder.specify "Two Columns Are Not Equal in Row 3" <|
expected_column = Column.from_vector "My Column" ["Quis", "custodiet", "ipsos", "custodes?"]
actual_column = Column.from_vector "My Column" ["Quis", "custodiet", "ipsos", "guards?"]
res = Panic.recover Test_Failure_Error (column_should_equal_impl actual_column expected_column "LOCATION_PATH")
res.catch.message.should_equal 'Column: My Column differs at row 3.\n\t Actual : guards?\n\t Expected: custodes?\n\t (at LOCATION_PATH).'
group_builder.specify "Two Columns with different types Are Not Equal" <|
expected_column = Column.from_vector "Col" ["1", "2", "3", "4"]
actual_column = Column.from_vector "Col" [1, 2, 3, 4]
res = Panic.recover Test_Failure_Error (column_should_equal_impl actual_column expected_column "LOCATION_PATH")
res.catch.message.should_equal "Expected column type (Char Nothing True), but got (Integer 64 bits) (at LOCATION_PATH)."
group_builder.specify "Comparing a Column to non column" <|
expected_column = 42
actual_column = Column.from_vector "Col" [1, 2, 3, 4]
res = Panic.recover Test_Failure_Error (column_should_equal_impl actual_column expected_column "LOCATION_PATH")
res.catch.message.should_equal "Got a Column, but expected a 42 (at LOCATION_PATH)."
group_builder.specify "Two Columns Containg NaNs Are Equal" <|
# This is somewhat of a special case, as NaN != NaN but for the purposes of testing we consider them equal
expected_column = Column.from_vector "Col" [1.0, 2.0, Number.nan]
actual_column = Column.from_vector "Col" [1.0, 2.0, Number.nan]
actual_column.should_equal expected_column
suite_builder.group "Table should_equal" group_builder->
group_builder.specify "Two Tables Are Equal" <|
expected_table = Table.new [Column.from_vector "Col1" ["Quis", "custodiet", "ipsos", "custodes?"], Column.from_vector "Col2" ["Who", "guards", "the", "guards?"]]
actual_table = Table.new [Column.from_vector "Col1" ["Quis", "custodiet", "ipsos", "custodes?"], Column.from_vector "Col2" ["Who", "guards", "the", "guards?"]]
actual_table.should_equal expected_table
group_builder.specify "Two Tables With Different Values" <|
expected_table = Table.new [Column.from_vector "Col1" ["Quis", "custodiet", "ipsos", "custodes?"], Column.from_vector "Col2" ["Who", "guards", "the", "guards?"]]
actual_table = Table.new [Column.from_vector "Col1" ["Quis", "custodiet", "ipsos", "custodes?"], Column.from_vector "Col2" ["Who", "guards", "teh", "guards?"]]
res = Panic.recover Test_Failure_Error (table_should_equal_impl actual_table expected_table "LOCATION_PATH")
res.catch.message.should_end_with 'Column: Col2 differs at row 2.\n\t Actual : teh\n\t Expected: the\n\t.'
group_builder.specify "Tables different number of columns" <|
expected_table = Table.new [Column.from_vector "Col1" ["Quis", "custodiet", "ipsos", "custodes?"]]
actual_table = Table.new [Column.from_vector "Col1" ["Quis", "custodiet", "ipsos", "custodes?"], Column.from_vector "Col2" ["Who", "guards", "the", "guards?"]]
res = Panic.recover Test_Failure_Error (table_should_equal_impl actual_table expected_table "LOCATION_PATH")
res.catch.message.should_end_with "Expected 1 columns, but got 2."
group_builder.specify "Tables different number of columns2" <|
expected_table = Table.new [Column.from_vector "Col1" ["Quis", "custodiet", "ipsos", "custodes?"], Column.from_vector "Col2" ["Who", "guards", "the", "guards?"]]
actual_table = Table.new [Column.from_vector "Col1" ["Quis", "custodiet", "ipsos", "custodes?"]]
res = Panic.recover Test_Failure_Error (table_should_equal_impl actual_table expected_table "LOCATION_PATH")
res.catch.message.should_end_with "Expected 2 columns, but got 1."
group_builder.specify "Tables With Mismatched Column names" <|
expected_table = Table.new [Column.from_vector "Col1" ["Quis", "custodiet", "ipsos", "custodes?"], Column.from_vector "Col2" ["Who", "guards", "the", "guards?"]]
actual_table = Table.new [Column.from_vector "Col" ["Quis", "custodiet", "ipsos", "custodes?"], Column.from_vector "Col2" ["Who", "guards", "the", "guards?"]]
res = Panic.recover Test_Failure_Error (table_should_equal_impl actual_table expected_table "LOCATION_PATH")
res.catch.message.should_end_with "Expected column name Col1, but got Col."
group_builder.specify "Comparing a Table to non Table" <|
expected_table = 42
actual_table = Table.new [Column.from_vector "Col1" ["Quis", "custodiet", "ipsos", "custodes?"]]
res = Panic.recover Test_Failure_Error (table_should_equal_impl actual_table expected_table "LOCATION_PATH")
res.catch.message.should_equal "Got a Table, but expected a 42 (at LOCATION_PATH)."
main filter=Nothing =
suite = Test.build suite_builder->
add_specs suite_builder
suite.run_with_filter filter