Friday, November 2, 2012

FEST Assertions for Joda Time

Do you write unit tests? Of course you do. Do you use Joda Time? I think so. Do you use FEST Assertions? You should try it if you haven't yet. With FEST Assertions we can write fluent code like this:
assertThat(result).isEqualTo(expected);
assertThat(testRunSeconds).isLessThan(maxTestRunSeconds);
assertThat(someList).isNotNull().hasSize(3).contains("expectedEntry");
Now let's assume we have some functions that return joda DateTime, and we want to test it. Can we do this in FEST Assertions?
assertThat(resultDateTime).isAfter(timeframeBeginning).isBefore(timeframeEnd);
No, we can't :( FEST Assertions don't handle Joda Time classes. However, do not worry :) At SoftwareMill we have written our own TimeAssertions for that :) So you can write your code like this:
TimeAssertions.assertTime(someTime).isAfterOrAt(someOtherTime);
TimeAssertions works for org.joda.time.DateTime, java.util.Date and org.joda.time.LocalDateTime. You can freely exchange DateTime and Date, i.e. you can compare DateTime to Date, DateTime to DateTime etc. LocalDateTime can be compared only to instances of the same class, as it doesn't make sense to compare it to DateTime or Date without specifying the time zone. TimeAssertions is available on github. If you want to use it from Maven project, add repository:
<repository>
    <id>softwaremill-releases</id>
    <name>SoftwareMill Releases</name>
    <url>http://tools.softwaremill.pl/nexus/content/repositories/releases</url>
</repository>
And dependency:
<dependency>
    <groupId>pl.softwaremill.common</groupId>
    <artifactId>softwaremill-test-util</artifactId>
    <version>70</version>
</dependency>
Happy testing!

Wednesday, September 26, 2012

Get script's own directory in bash script

It was always a problem for me to get the directory the called script is stored in, in the script itself. Thanks to this SO question (http://stackoverflow.com/questions/59895/can-a-bash-script-tell-what-directory-its-stored-in) it's not a problem anymore. As it says:
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
Or, to get the dereferenced path (all directory symlinks resolved), do this:
DIR="$( cd -P "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"

Saturday, September 15, 2012

Get specific PC IP from "arp -a"

I want to extract IP of machine leonidas. arp -a returns such line (among others): 
leonidas.home (192.168.1.5) at 0:1c:c0:de:8f:28 on en1 ifscope [ethernet] 

To have only IP:
arp -a | grep leonidas | cut -f 2 -d ' ' | sed 's/[()]//g'
prints
192.168.1.5

Monday, January 9, 2012

My encounter with a small bug in Hibernate

The problem

At work, I needed to use entities with @DiscriminatorColumn inheritance. It means all types are kept in the same table, with value in this column showing what type given row is of. It's not recommended way to handle inheritance, but for some reasons we needed to use it. In developement, locally, I was using PostgreSQL database. When I tried to store this entities, I was receiving strange errors. Saying I cannot store an entity because entity with such id is already in database. It was quite strange, I was trying to store vanilla new entity. Test case to show this error is very short, so I'll include it here:
//Parent entity
@Entity
@Inheritance(strategy = SINGLE_TABLE)
@DiscriminatorColumn(name = "CLASS_ID", discriminatorType = INTEGER)
public abstract class ParentEntity {
  @Id
  @GeneratedValue(strategy = IDENTITY)
  private Long id;
}

//Child entity with discriminator  
@Entity
@DiscriminatorValue("1")
public class InheritingEntity extends ParentEntity {
}

//Test
public class PersistChildEntitiesWithDiscriminatorTest extends BaseCoreFunctionalTestCase {
  
  @Test
  public void shouldPersistTwoEntities() {
    Session session = openSession();
    session.beginTransaction();
    InheritingEntity child1 = new InheritingEntity();
    InheritingEntity child2 = new InheritingEntity();
    session.save(child1);
    session.save(child2);
    session.getTransaction().rollback();
  }
}

The cause

This test throws exception on second save, but only on PostgreSQL. Why is that? Well, when you save new entity to persistence context, Hibernate issues SQL call to database instantly. Other queries, like updates, are cached, and sent to database on em.flush or em.commit. But inserting of new entities is not cached and there is a reason for that. When we save new entity, Hibernate needs to assign ID to it, and this is taken from database. Most databases return ResultSet with one row and one column after insert, and it contains newly assigned ID. However, PostgreSQL behaves a bit differently. It returns whole inserted row (of course, with ID filled in). In most cases it works, because ID is the first column in this row, so when Hibernate takes value from the first row and the first column, it is the correct one. However, in case of classes with discriminator, ID is not the first column. Discriminator is the first column. So first insert is correct, ID 1 is assigned to child1, but then when we try to store child2, Hibernate also tries to assign 1 to it's ID, and complaints that there already is another entity with it.

The solution

So there was a bug in Hibernate. Can I solve it? I asked this question to myself, but to answer I couldn't do anything else than try ;) So I forked hibernate repository (yes, it's on github!) and... I was quite overwhelmed by the mass of code there. First challenge was to try to open it in my IDE, with all the subprojects and their interdependencies configured correctly. Thankfully there is gradle task for creating project files for IntelliJ IDEA, the IDE I'm happy user of. Next task was configuring Hibernate tests to use my PostgreSQL database. It turned out quite easy after one or two emails on hibernate-dev list. Now I had to change the code assigning IDs to entities to take it not always from first column first row, but sometimes from column of given name. So I had to get the name of column keeping IDs, which I did with a little help from other developers on the dev list.

The contribution

Now I commited fix to my forked repository on github, issued a pull request, got some comments, fixed files formatting... We'll see if it's accepted. UPDATE: It is accepted :)