Friday, July 28, 2017

Debug for Mocha tests in Visual Studio Code

I was surprised  when I found how it's hard to set up such simple thing as "debug" in Visual Studio Code. It's unclear at all! For debug configuration we have just one json file and what exactly should be placed there - nobody knows. Additional problem were caused by using Babel ES2015 in code.

So, that exactly have to be done:

1. launch.json

Create file if it doesn't exists:
.vscode\launch.json

and place there:

{
    "version": "0.2.0",
    "configurations": [
        {
            "request": "launch",
            "name": "Debug Mocha Test",
            "type": "node",
            "program": "${workspaceRoot}/node_modules/mocha/bin/_mocha",
            "stopOnEntry": true,
            "args": [
                "--compilers",
                "js:babel-core/register",               
                "--recursive"
            ],
            "cwd": "${workspaceRoot}/.",
            "runtimeExecutable": null,
            "env": {}
        }
    ]
}


2. Put breackpoint and run debug by pressing F5

And this is basically it - debug should work now: 




3. The end

As a result, it was very simple, but it took several  hours of time to understand what exactly has to be done :)

Tuesday, July 11, 2017

Selenium Java getting started

1. Intro

What is selenium?

From wiki:
Selenium is a portable software-testing framework for web applications. Selenium provides a record/playback tool for authoring tests without the need to learn a test scripting language (Selenium IDE). It also provides a test domain-specific language (Selenese) to write tests in a number of popular programming languages, including C#GroovyJavaPerlPHPPythonRuby and Scala. The tests can then run against most modern web browsers. Selenium deploys on WindowsLinux, and OS X platforms. It is open-source software, released under the Apache 2.0 license: web developers can download and use it without charge.

Let's try to do some simple tasks using this library. I want to use if for testing my simple ReactJS application, which I created week ago.


2. How it works. 

With selenium we can "emulate" all user actions we need: entering text into input fields, clicking the buttons, checking checkboxes and many others. But first of all, we need to find somehow element for which we want to perform such actions. In selenium there are a lot of options for doing this:

  • by id
  • by name
  • by tag name 
  • ....




In this post I'm using the simplest way: by element id.
Example: driver.findElement(By.id("submit-department").click();

Here is a list of html elements which I will be accessing by theirs ids:



3. SetUp

There are just few things to start working with selenium:

  • first of all we have to add to our project selenium dependency. Example for  gradle:

      compile group: 'org.seleniumhq.selenium', name: 'selenium-java', version: '2.41.0'
  • also we need the driver for your favorite browser. For example for ChromeBrowser you can download it from here. You have have to unpack it and put it somewhere in file system. 
  • And finally, driver binary from previous step, have to be set up as system property in your code this way:
System.setProperty("webdriver.chrome.driver", "d:/dev/selenium/chromedriver.exe");

After that we can try to create the instance of driver and open something with the browser:
WebDriver driver = new ChromeDriver();
driver.get("http://localhost:3000");

-it should run the browser and navigate it to page from code above.



4. Testing 

I wrote very simple application which creates several departments and after that several users. And users creation goes using departments created before. At the end I'm checking if all departments and users were created.


package com.demien.seltest;

import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.support.ui.Select;

import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;

public class App {

    static WebDriver driver;

    public static void clearAndSet(String id, String value) {
        driver.findElement(By.id(id)).clear();
        driver.findElement(By.id(id)).sendKeys(value);
    }

    public static void click(String id) {
        driver.findElement(By.id(id)).click();
    }

    public static void select(String id, String option) {
        Select select = new Select(driver.findElement(By.id(id)));
        select.selectByVisibleText(option);
    }

    public static void addDepartment(String id, String name) {
        clearAndSet("input-departments-id", id);
        clearAndSet("input-departments-name", name);
        click("submit-departments");
    }

    public static void addUser(String id, String name, String email, String department) {
        clearAndSet("input-users-id", id);
        clearAndSet("input-users-name", name);
        clearAndSet("input-users-email", email);
        select("select-users-department", department);
        click("submit-users");
    }

    public static void checkTable(String id, List<String> values) {
        WebElement table = driver.findElement(By.id(id));

        List<WebElement> allRows = table.findElements(By.tagName("tr"));
        for (WebElement row : allRows) {
            List<WebElement> cells = row.findElements(By.tagName("td"));
            for (WebElement cell : cells) {
                values.remove(cell.getText());
            }
        }

        if (values.size() > 0) {
            throw new RuntimeException("Some elements are absent in table:" + Arrays.toString(values.toArray()));
        }
    }

    public static void checkTable(String id, String... values) {
        checkTable(id, new LinkedList<>(Arrays.asList(values)));
    }

    public static void main(String[] args) {
        System.setProperty("webdriver.chrome.driver", "d:/dev/selenium/chromedriver.exe");
        driver = new ChromeDriver();

        driver.get("http://localhost:3000");

        addDepartment("IT", "Informational technologies");
        addDepartment("ADM", "Administration");
        addDepartment("SEQ", "Security");

        checkTable("table-departments", "IT", "ADM", "SEQ", "Informational technologies", "Administration", "Security");

        addUser("JBL", "Joe Black", "joe.black@email.com", "Security");
        addUser("HSEB", "Huan Sebastyan", "huan.sebastyan@email.com", "Administration");

        checkTable("table-users", "JBL", "Joe Black", "joe.black@email.com", "Security", "HSEB", "Huan Sebastyan", "huan.sebastyan@email.com", "Administration");

    }
}

5. The end 

As a result, using just code from above, I created 3 departments and 2 users.




Source code can be downloaded from here

Saturday, July 1, 2017

Pessimistic and optimistic locking in Hibernate

1. Intro


When data can be updated by different sessions, sometimes we have to make some additional checking if data are consistent.

From jboss documentation:

Locking is perhaps the most ignored persistence consideration. Most applications tend to ignore thinking about concurrency issues during development, and then smush in a locking mechanism before going into production. Considering the large percentage of software projects that fail or are canceled, or never achieve a large user base, perhaps this is logical. However, locking and concurrency is a critical or at least a very important issue for most applications, so probably should be something considered earlier in the development cycle.

Optimistic locking assumes that the data will not be modified between when you read the data until you write the data. This is the most common style of locking used and recommended in today's persistence solutions. The strategy involves checking that one or more values from the original object read are still the same when updating it. This verifies that the object has not changed by another user in between the read and the write.

Pessimistic locking means acquiring a lock on the object before you begin to edit the object, to ensure that no other users are editing the object. Pessimistic locking is typically implemented through using database row locks, such as through the SELECT ... FOR UPDATE SQL syntax. The data is read and locked, the changes are made and the transaction is committed, releasing the locks.


2. Application structure

All code related with locking is placed in TEST source set. In MAIN - just HibernateUtil and DTO files.



3. build.gradle


Here I have compile dependencies for hibernate and mysql. And test dependencies for slf4j, spock, junit.

group 'com.demien.hibernate'version '1.0-SNAPSHOT'
apply plugin: 'groovy'apply plugin: 'java'
sourceCompatibility = 1.8
repositories {
    mavenCentral()
}

dependencies {
    compile 'org.hibernate:hibernate-core:5.2.10.Final'    
    compile group: 'mysql', name: 'mysql-connector-java', version: '5.1.6'

    testCompile group: 'junit', name: 'junit', version: '4.12'    
    testCompile group: 'org.spockframework', name: 'spock-core', version: '1.1-groovy-2.4'    
    testCompile group: 'org.slf4j', name: 'slf4j-log4j12', version: '1.7.25'
}


4. Hibernate Util

Just regular settings for MySQL connection and 3 dto files with annotations which will be  used for testing: Role, User and UserGroup.

package com.demien.hiblock.db;

import com.demien.hiblock.dto.Role;
import com.demien.hiblock.dto.User;
import com.demien.hiblock.dto.UserGroup;
import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
import org.hibernate.cfg.Configuration;


public class HibernateUtil {

    private static SessionFactory sessionFactory;

    static {
        final Configuration configuration = new Configuration();
        configuration.addAnnotatedClass(User.class);
        configuration.addAnnotatedClass(UserGroup.class);
        configuration.addAnnotatedClass(Role.class);

        configuration.setProperty("hibernate.connection.driver_class", "com.mysql.jdbc.Driver");
        configuration.setProperty("hibernate.connection.url", "jdbc:mysql://localhost:3306/test");
        configuration.setProperty("hibernate.connection.username", "test");
        configuration.setProperty("hibernate.connection.password", "test");
        configuration.setProperty("hibernate.dialect", "org.hibernate.dialect.MySQL5InnoDBDialect");
        configuration.setProperty("hibernate.hbm2ddl.auto", "update");
        configuration.setProperty("hibernate.show_sql", "true");

        StandardServiceRegistryBuilder builder = new StandardServiceRegistryBuilder().applySettings(configuration.getProperties());
        sessionFactory = configuration.buildSessionFactory(builder.build());
    }

    public static Session getSession()
            throws HibernateException {
        return sessionFactory.openSession();
    }
}

5. Base test class

I used separate test files for locking types, so I moved common logic to BaseTest class.

package com.demien.hiblock

import com.demien.hiblock.db.HibernateUtil
import com.demien.hiblock.dto.Role
import com.demien.hiblock.dto.User
import com.demien.hiblock.dto.UserGroup
import org.hibernate.LockMode
import org.hibernate.LockOptions
import org.hibernate.resource.transaction.spi.TransactionStatus
import spock.lang.Specification

abstract class BaseTest extends Specification {
    def session = HibernateUtil.getSession()

    def setupSpec() {
        println "Starting"    }

    def cleanupSpec() {
        println "Done!"    }

    def setup() {
        doInTransaction({
            session.createQuery("delete from User").executeUpdate()
            session.createQuery("delete from UserGroup").executeUpdate()
            session.createQuery("delete from Role").executeUpdate()
        })
    }

    def cleanup() {
        session.clear()
    }

    def doInTransaction(f) {
        def tx = session.getTransaction()
        if (tx.getStatus().equals(TransactionStatus.ACTIVE)) {
            tx.commit()
        }
        tx.begin()
        f()
        tx.commit()
    }

    def doInAnotherSession(f) {
        def oldSession = session        session = HibernateUtil.getSession()
        f()
        session = oldSession
    }

    def doAsync(f) {
        def runnale = new Runnable() {
            @Override            void run() {
                f()
            }
        }
        def thread = new Thread(runnale)
        thread.start()

    }

    def createEntity(entity) {
        doInTransaction({
            session.persist(entity)
        })
    }

    def updateEntity(entity) {
        doInTransaction({
            session.merge(entity)
        })
    }

    def mergeEntity(entity) {
        doInTransaction({
            session.merge(entity)
        })
    }

    def updateEntityInAnotherSession(entity) {
        doInAnotherSession({ updateEntity(entity) })
    }

    def lockEntity(entity) {
        session.buildLockRequest(new LockOptions(LockMode.PESSIMISTIC_WRITE)).lock(entity)
    }

    def lockEntityInAnotherSession(entity) {
        doInAnotherSession({ lockEntity(entity) })
    }

    def createTestUser() {
        createEntity(new User(1l, "Huan Sebastyan"))
    }

    def loadTestUser() {
        return session.get(User.class, 1L)
    }

    def createTestGroup() {
        createEntity(new UserGroup(1L, "SYSDBA", "Database Administrators"))
    }

    def loadTestGroup() {
        return session.get(UserGroup.class, 1L)
    }

    def createTestRole() {
        createEntity(new Role(1L, "SYSDBA", "Admin role"))
    }

    def loadTestRole() {
        return session.get(Role.class, 1L)
    }

}

6. Optimistic locking

As it was mentioned before, in  optimistic locking strategy we are not locking data physically, we are just checking was data modified before our update or not. We can do that in 2 ways:

  • by version checking - if version is the same - record was not chenged
  • by all fields checking - if all fields remained the same - record was not changed


6.1 by Version

In entity class we have to annotate one field with @Version. This field will be incremented by hibernate every time we update the record.

@Entity
public class User {

    @Id    
    @Column    
    private Long userId;

    @Column    
    private String userName;

    @Version    
    private Long version;



Now let's write the test:

def "it should fail if record was changed by another session"() {
    given:
    createTestUser()
    User loadedUser1 = loadTestUser()
    User loadedUser2 = loadTestUser()
    loadedUser1.setUserName("Updated1")
    loadedUser2.setUserName("Updated2")

    when:
    updateEntityInAnotherSession(loadedUser1)
    updateEntity(loadedUser2)

    then:
    thrown OptimisticLockException

}

In output window we will find:
Hibernate: insert into User (userName, version, userId) values (Huan Sebastyan, 0, 1)
Hibernate: select user0_.userId as userId1_1_0_, user0_.userName as userName2_1_0_, user0_.version as version3_1_0_ from User user0_ where user0_.userId=1
Hibernate: update User set userName=Updated2, version=1 where userId=1 and version=0
Hibernate: update User set userName=Updated2, version=1 where userId=1 and version=0

Hibernate automatically added version fields to UPDATE and WHERE clauses. As a result, last update will fail because record was updated in another session and now it has version 1 , not 0.

6.2 by all fields checking

To entity class we have to add additional annotations:
@Entity
@DynamicUpdate
@OptimisticLocking(type = OptimisticLockType.DIRTY)
public class UserGroup {

    @Id    
    @Column    
    private Long groupId;

    @Column    
    private String groupName;

    @Column    
    private String groupDescription;



Let's write the test:
def "it should fail if record was changed by another session"() {
    given:
    createTestGroup()
    UserGroup loadedGroup1 = loadTestGroup()
    UserGroup loadedGroup2 = loadTestGroup()
    loadedGroup1.setGroupName("Updated name")
    loadedGroup2.setGroupDescription("Updated description")


    when:
    updateEntityInAnotherSession(loadedGroup1)

    updateEntity(loadedGroup2)

    then:
    thrown OptimisticLockException
}


In output we will find:
Hibernate: insert into UserGroup (groupDescription, groupName, groupId) values (Database Administrators, SYSDBA, 1)
Hibernate: select usergroup0_.groupId as groupId1_2_0_, usergroup0_.groupDescription as groupDes2_2_0_, usergroup0_.groupName as groupNam3_2_0_ from UserGroup usergroup0_ where usergroup0_.groupId=1
Hibernate: update UserGroup set groupDescription=Updated description, groupName=Updated name where groupId=1 and groupDescription=Database Administrators and groupName=SYSDBA
Hibernate: update UserGroup set groupDescription=Updated description, groupName=Updated name where groupId=1 and groupDescription=Database Administrators and groupName=SYSDBA

Hibernate automatically adds changed fields to were clause. So if field was changed by another session - it will not match were clause, records will not be updated.

7. Pessimistic locking

Pessimistic locking is applied on Database level. It "physically" locks updated record. And if other session is trying to update it - it has to wait till locking session commits changes. In MySQL for such locking used SELECT ... FOR UPDATE syntax.
In test provided below, we lock record and after that we are trying to update it in another session.
This another session will hung and after 1 minute if will fail by timeout with LockTimeout exception.


def "it should throw the exception on trying to update locked record"() {
    given:
    createTestRole()
    Role role1 = loadTestRole()

    lockEntity(role1)

    when:
    doInAnotherSession({

        Role role2 = loadTestRole()
        role2.setRoleDescription("Updated description")
        mergeEntity(role2)

    })

    then:
    thrown LockTimeoutException

}

Output:
Hibernate: insert into Role (roleDescription, roleName, roleId) values (?, ?, ?)
Hibernate: select roleId from Role where roleId =? for update
Hibernate: select role0_.roleId as roleId1_0_0_, role0_.roleDescription as roleDesc2_0_0_,
Hibernate: update Role set roleDescription=?, roleName=? where roleId=?
12:59:15,754  WARN SqlExceptionHelper:129 - SQL Error: 1205, SQLState: 41000
12:59:15,755 ERROR SqlExceptionHelper:131 - Lock wait timeout exceeded; try restarting transaction
12:59:15,756  INFO AbstractBatchImpl:193 - HHH000010: On release of batch it still contained JDBC statements
12:59:15,756 ERROR ExceptionMapperStandardImpl:39 - HHH000346: Error during managed flush [could not execute statement]


As you can see from logs, by SELECT for UPDATE record was locked. And UPDATE in another session was not update to update the record.


8. The end.

Source code can be downloaded from here.