Skip to content

A few modifications to LongArray and a test class for it #7516

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

Closed
wants to merge 29 commits into from

Conversation

melvilpf
Copy link
Contributor

I modified three methods of the LongArray class:

  • lastIndexOf took a char argument when it was supposed to be a long since we are working with long arrays ;
  • pop() and peekl() did not handle the case of empty arrays so I added this case.

I then implemented a test class for LongArray, testing all the methods I could (of course I can't test random() for example).

I also apologize, I did not want the iosgl20 and iosgl30 to be modified and couldn't remove the modifications.

@@ -272,11 +272,13 @@ public boolean removeAll (LongArray array) {

/** Removes and returns the last item. */
public long pop () {
if (size == 0) throw new IllegalStateException("Array is empty.");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch here; calling pop() on an empty LongArray would produce a negative size and only then throw an Exception. Catching that would still mean dealing with negative size later!

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One possible nitpick, now that I think about it, is that if the IllegalStateException is caught once, and pop() is called again, it will throw an IndexOutOfBoundsException just after the catch, but won't throw an IllegalStateException a second time, because size will be -1, and that won't be == 0. We might prefer size <= 0 because the size can be set externally, plus this method can make the size negative, and all sizes 0 or less are invalid here.

/** Test of the indexOf() method */
@Test
public void indexOfTest () {
LongArray longArray1 = new LongArray(new long[] {1, 3, 4, 5, 6, 6, 3, 9});
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a little odd that the fix was for lastIndexOf() taking a char parameter, when it should have been long, but all of the tested values would still be valid before the fix. If some were negative or greater than 65535, then this would be a better test of the change that was just made.

Copy link
Member

@tommyettinger tommyettinger left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The changes look mostly good to me, with some tiny issues. I think the pop() and peek() checks should see if size <= 0 rather than size == 0 because size can be assigned a negative value externally or by catching exceptions and letting size change in pop().

The test seems fine; it is only really testing smaller int values (within the range of shorts, it looks like), so the change in this PR to lastIndexOf() could have escaped notice, but maybe not... I don't have an issue with the tests right now, it's good to have them.

So, wrapping up, changing size == 0 to size <= 0 if both negative values and 0 are invalid would be good. This could be made in other places too, since it shouldn't break any valid operations, and will just notice problems hopefully before they happen elsewhere.

…and not size==0 anymore, the test method for lastIndexOf(long) has been modified to test numbers > 65 535 to test the fact that we changed the parameter from char to long
@melvilpf
Copy link
Contributor Author

Hi, I've just made the changes requested. Also, I'm pretty new to GitHub and don't know if I should click on Update branch because I have a warning "This branch is out-of-date with the base branch".

@melvilpf
Copy link
Contributor Author

I will also maybe be testing shuffle and random with stats but tell me if that's relevant before I begin.

tommyettinger
tommyettinger previously approved these changes Nov 21, 2024
@tommyettinger
Copy link
Member

I approved it as-is, it looks totally fine now! I don't feel like shuffle() and random() need to be tested, but if you do decide to test them, at the start of each test case, use MathUtils.random.setSeed(12345); (or some constant long seed other than 12345, if you find one you want to be reproducible). Using the same seed every time means there's no chance of really rare output sequences that appear to be statistically unlikely happening on one test run but not most test runs. It's a good idea to set the seed at the start of the test case, but run the shuffle() or random() methods many times, like 1000, and just make sure each item in a small array appears at least once in the first and at least once in the last position (for shuffle) or just that each item is returned at least once (for random). You shouldn't check that the output always is the same for a given seed, since future versions might change the algorithm MathUtils.random employs (it is currently a RandomXS128).

@melvilpf
Copy link
Contributor Author

Okay, awesome and thank you for the tip !

Copy link
Contributor

@Frosty-J Frosty-J left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I respectfully disagree with TEt. I thought he liked hash codes. My concerns are a mix of subjectivity and objectivity.

Copy link
Contributor

@Frosty-J Frosty-J Nov 21, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this is to be merged, a member must remember to fix these huge diffs first.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, it was a mess but I think I've fixed it

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks. @melvilpf you should git pull this before making any further changes, otherwise I don't think it'll be able to push them due to the rebase against libgdx:master. I could be saying some wrong things but you get the memo.

@Test
public void addTest () {
/*
* Test of the classic add(long value) method, it should be adding a number at the first available place.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At risk of repeating what I said at #7514 (comment), the comments throughout this seem more plentiful and verbose than they need to be. It's irrelevant whether a method is classic or otherwise, and there's a lot of "Test of the ... method" in here stating the obvious. Since multiple tests are performed in this test, it's not unreasonable to break it up with comments, but many would argue that unit tests are supposed to be as small as possible. Since they are run automatically, it doesn't matter much if there's loads of them.

LongArray longArray3 = new LongArray();
longArray3.addAll(longArray2);
Assert.assertArrayEquals(longArray2.toArray(), longArray3.toArray());
longArray3.addAll(longArray1);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I find this quite difficult to read and relying on the results of the previous tests weakens this. I would include {3} and {1, 2} directly in this test. obigu might agree? #7518 (comment)

* int and can't be calculated if >= 2 147 483 647 (because negative numbers are defined by the first bit - The result of 1
* 193 508 601 * 31 + 400 is 36 998 767 031 (1000 1001 1101 0100 1100 0110 0001 1011 0111) which is, truncated to 32 bits,
* -1 655 938 633 (1001 1101 0100 1100 0110 0001 1011 0111) - Iteration 7 (item = 400): h = - 1 655 938 633 - Same here,
* without redetailing the operation, we get, truncated to 32 bits, 205 510 429 - Iteration 8 (item = 500): h = 205 510 429
Copy link
Contributor

@Frosty-J Frosty-J Nov 21, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understand Spotless probably did this, but I find it very difficult to read as the - used for bullet points ends up looking like mathematical subtraction. This looks like a complete sentence on first read: Start with h = 1 2. You may wish to consider using <ol> and <li> to create a numbered list. If this was generated with AI and you haven't gone through it yourself, I urge you to just throw it away.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, for one thing testing hash codes this way doesn't actually provide any validation of any properties of a hashing function... for another thing, hashCode() impls in Java are usually quite simple, and that's fine for their typical usage in hash tables. The main requirement for a hash function to use in a hash table is just for the function to be fast to call (as in, a small bytecode size); most String keys aren't novel-length texts, and a LongArray used as a key in a HashMap probably won't have 1000+ items, so the actual performance of the function per item doesn't matter as much as if it can be inlined early, and the quality can be actually quite bad and still work fine in a hash table.

I think the whole hashCodeTest() should be removed.

Object longArrayUnordered = new LongArray(false, 16);
((LongArray)longArrayUnordered).add(2, 1, -6, 2);
((LongArray)longArrayUnordered).add(5, 50, 34, 42);
Assert.assertEquals(longArrayUnordered.hashCode(), ((LongArray)longArrayUnordered).hashCode());
Copy link
Contributor

@Frosty-J Frosty-J Nov 21, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This assertion is useless. LongArray#hashCode overrides Object#hashCode - you are comparing the same result in each case. I can change its body to this and it will still pass that part:

public int hashCode () {
	return 1;
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Honestly any test on hashCode() is probably pointless. I say get rid of the hash code test.

Copy link
Member

@tommyettinger tommyettinger left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm mostly requesting that hashCodeTest() be removed, since I don't feel like the ways hashCode() can be tested actually help the class. I also feel like toStringTest() can be removed.

* int and can't be calculated if >= 2 147 483 647 (because negative numbers are defined by the first bit - The result of 1
* 193 508 601 * 31 + 400 is 36 998 767 031 (1000 1001 1101 0100 1100 0110 0001 1011 0111) which is, truncated to 32 bits,
* -1 655 938 633 (1001 1101 0100 1100 0110 0001 1011 0111) - Iteration 7 (item = 400): h = - 1 655 938 633 - Same here,
* without redetailing the operation, we get, truncated to 32 bits, 205 510 429 - Iteration 8 (item = 500): h = 205 510 429
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, for one thing testing hash codes this way doesn't actually provide any validation of any properties of a hashing function... for another thing, hashCode() impls in Java are usually quite simple, and that's fine for their typical usage in hash tables. The main requirement for a hash function to use in a hash table is just for the function to be fast to call (as in, a small bytecode size); most String keys aren't novel-length texts, and a LongArray used as a key in a HashMap probably won't have 1000+ items, so the actual performance of the function per item doesn't matter as much as if it can be inlined early, and the quality can be actually quite bad and still work fine in a hash table.

I think the whole hashCodeTest() should be removed.

/** Test of the set() method */
@Test
public void setTest () {
LongArray longArray = new LongArray(new long[] {3, 4, 5, 7});
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All these usages of the LongArray constructor that takes a long[] could be a good amount cleaner by using LongArray.with(3, 4, 5, 7) . It isn't at all needed, but I feel like with() should at least be known about, and used when possible.


/** Test of the toString() methods */
@Test
public void toStringTest () {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm also reiterating Frosty-J's concern about the toString format being flexible to change; we might want to switch to {} instead of [] to work as an array literal, and append L to each number so this could go into source code. I don't think that's terribly likely, but it shouldn't be codified in a test.

@melvilpf
Copy link
Contributor Author

Okay, so I'm getting rid of hashCodeTest() and should I also get rid of the toString() tests ? I will also be using at least once LongArray.with(...), is that it ?

@tommyettinger
Copy link
Member

Okay, so I'm getting rid of hashCodeTest() and should I also get rid of the toString() tests ? I will also be using at least once LongArray.with(...), is that it ?

Sounds good to me. LongArray.with() is just a good API design IMO, one that the actual JDK didn't even have until Java 10 or 11 (where I think it's List.of() for some form of List implementation). I feel like using it to create a LongArray makes sense when you know the initial contents right away. Internally, it's essentially the same, a long[] is created automatically for with(), or explicitly with the constructor you have now, and it gets passed to the same constructor that takes a long[] . Using with() tends to be shorter and look cleaner, though it does hide that an array is being created.

It does make sense to at least lightly test with(), because some of these varargs methods have corner cases that should be checked against, like how you are allowed to do LongArray.with(null), which is passing a long[] that is null, and it gets to the constructor before failing when it tries to get the .length of null. For object items, what should Array.with() (giving it no arguments) return? Currently, because it has no item to figure out what T is in T..., it returns an Array<Object> .

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How did 953da77 return? :(

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh no did it ? ://
I git pulled though that's so weird, and it doesn't appear in my commits

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My advice not to waste time with it is, close this PR, which is a mess, and create a new clean one with all the changes from comments already applied.

@melvilpf
Copy link
Contributor Author

Okay, I'm closing this pull request and opening a new one, thanks for the help

@melvilpf melvilpf closed this Nov 24, 2024
@melvilpf melvilpf deleted the tests2 branch November 24, 2024 07:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

10 participants