DATAREDIS-245 - Prevent Excessive Thread creation in tests.

Due to some missing cleanup procedures excessive thread creation lead to JVM crashes during a full test run on some machines.
Improved test harness in order to properly shutdown unused threads.
Rewritten testPubSubWithNamedChannels in order to make it pass reliably by keeping the original test semantics.
Rewritten testPubSubWithPatterns in order to make it pass reliably by keeping the original test semantics.
This commit is contained in:
Thomas Darimont
2014-01-13 20:07:01 +01:00
parent bf8facfd51
commit f425925dd5
10 changed files with 210 additions and 133 deletions

2
.gitignore vendored
View File

@@ -13,4 +13,4 @@ pom.xml
.project
.settings
.idea
out
out

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2011-2013 the original author or authors.
* Copyright 2011-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,15 +16,6 @@
package org.springframework.data.redis.connection.jedis;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.BlockingDeque;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.TimeUnit;
import org.junit.After;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -32,25 +23,32 @@ import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.data.redis.SettingsUtils;
import org.springframework.data.redis.connection.AbstractConnectionIntegrationTests;
import org.springframework.data.redis.connection.ConnectionUtils;
import org.springframework.data.redis.connection.DefaultStringRedisConnection;
import org.springframework.data.redis.connection.DefaultStringTuple;
import org.springframework.data.redis.connection.Message;
import org.springframework.data.redis.connection.MessageListener;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.ReturnType;
import org.springframework.data.redis.connection.StringRedisConnection;
import org.springframework.data.redis.connection.StringRedisConnection.StringTuple;
import org.springframework.test.annotation.IfProfileValue;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import redis.clients.jedis.JedisPoolConfig;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.BlockingDeque;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.TimeUnit;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
/**
* Integration test of {@link JedisConnection}
*
* @author Costin Leau
* @author Jennifer Hickey
* @author Thomas Darimont
*
*/
@RunWith(SpringJUnit4ClassRunner.class)
@@ -61,13 +59,19 @@ public class JedisConnectionIntegrationTests extends AbstractConnectionIntegrati
public void tearDown() {
try {
connection.flushDb();
connection.close();
} catch (Exception e) {
// Jedis leaves some incomplete data in OutputStream on NPE caused
// by null key/value tests
// Attempting to flush the DB or close the connection will result in
// error on sending QUIT to Redis
}
try{
connection.close();
} catch (Exception e) {
//silently close connection
}
connection = null;
}
@@ -276,62 +280,72 @@ public class JedisConnectionIntegrationTests extends AbstractConnectionIntegrati
super.testErrorInTx();
}
// Override pub/sub test methods to use a separate connection factory for
// subscribing threads, due to this issue: https://github.com/xetorthio/jedis/issues/445
/**
* Override pub/sub test methods to use a separate connection factory for
* subscribing threads, due to this issue: https://github.com/xetorthio/jedis/issues/445
*/
@Test
public void testPubSubWithNamedChannels() throws Exception {
final String expectedChannel = "channel1";
final String expectedChannel = "channel1";
final String expectedMessage = "msg";
final BlockingDeque<Message> messages = new LinkedBlockingDeque<Message>();
MessageListener listener = new MessageListener() {
public void onMessage(Message message, byte[] pattern) {
messages.add(message);
messages.add(message);
System.out.println("Received message '" + new String(message.getBody()) + "'");
}
};
JedisConnectionFactory factory2 = new JedisConnectionFactory();
factory2.setHostName(SettingsUtils.getHost());
factory2.setPort(SettingsUtils.getPort());
factory2.setUsePool(false);
factory2.afterPropertiesSet();
final StringRedisConnection nonPooledConn = new DefaultStringRedisConnection(factory2.getConnection());
Thread th = new Thread(new Runnable() {
public void run() {
// sleep 1/2 second to let the registration happen
try {
Thread.sleep(500);
} catch (InterruptedException ex) {
throw new RuntimeException(ex);
}
Thread t = new Thread(){
{
setDaemon(true);
}
public void run(){
// open a new connection
RedisConnection connection2 = connectionFactory.getConnection();
connection2.publish(expectedChannel.getBytes(), expectedMessage.getBytes());
connection2.close();
// In some clients, unsubscribe happens async of message
// receipt, so not all
// messages may be received if unsubscribing now.
// Connection.close in teardown
// will take care of unsubscribing.
if (!(ConnectionUtils.isAsync(connectionFactory))) {
nonPooledConn.getSubscription().unsubscribe();
}
}
});
th.start();
nonPooledConn.subscribe(listener, expectedChannel.getBytes());
// Not all providers block on subscribe, give some time for messages to
// be received
Message message = messages.poll(5, TimeUnit.SECONDS);
RedisConnection con = connectionFactory.getConnection();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
con.publish(expectedChannel.getBytes(),expectedMessage.getBytes());
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
/*
In some clients, unsubscribe happens async of message
receipt, so not all
messages may be received if unsubscribing now.
Connection.close in teardown
will take care of unsubscribing.
*/
if (!(ConnectionUtils.isAsync(connectionFactory))) {
connection.getSubscription().unsubscribe();
}
con.close();
}
};
t.start();
connection.subscribe(listener, expectedChannel.getBytes());
Message message = messages.poll(5, TimeUnit.SECONDS);
assertNotNull(message);
assertEquals(expectedMessage, new String(message.getBody()));
assertEquals(expectedChannel, new String(message.getChannel()));
}
}
@Test
public void testPubSubWithPatterns() throws Exception {
final String expectedPattern = "channel*";
final String expectedMessage = "msg";
final BlockingDeque<Message> messages = new LinkedBlockingDeque<Message>();
@@ -344,40 +358,43 @@ public class JedisConnectionIntegrationTests extends AbstractConnectionIntegrati
}
};
JedisConnectionFactory factory2 = new JedisConnectionFactory();
factory2.setHostName(SettingsUtils.getHost());
factory2.setPort(SettingsUtils.getPort());
factory2.setUsePool(false);
factory2.afterPropertiesSet();
final StringRedisConnection nonPooledConn = new DefaultStringRedisConnection(factory2.getConnection());
Thread th = new Thread(new Runnable() {
Thread th = new Thread(){
{
setDaemon(true);
}
public void run() {
// sleep 1/2 second to let the registration happen
try {
Thread.sleep(500);
} catch (InterruptedException ex) {
throw new RuntimeException(ex);
}
// open a new connection
RedisConnection connection2 = connectionFactory.getConnection();
connection2.publish("channel1".getBytes(), expectedMessage.getBytes());
connection2.publish("channel2".getBytes(), expectedMessage.getBytes());
connection2.close();
// open a new connection
RedisConnection con = connectionFactory.getConnection();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
con.publish("channel1".getBytes(), expectedMessage.getBytes());
con.publish("channel2".getBytes(), expectedMessage.getBytes());
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
con.close();
// In some clients, unsubscribe happens async of message
// receipt, so not all
// messages may be received if unsubscribing now.
// Connection.close in teardown
// will take care of unsubscribing.
if (!(ConnectionUtils.isAsync(connectionFactory))) {
nonPooledConn.getSubscription().pUnsubscribe(expectedPattern.getBytes());
connection.getSubscription().pUnsubscribe(expectedPattern.getBytes());
}
}
});
};
th.start();
nonPooledConn.pSubscribe(listener, expectedPattern);
connection.pSubscribe(listener, expectedPattern);
// Not all providers block on subscribe (Lettuce does not), give some
// time for messages to be received
Message message = messages.poll(5, TimeUnit.SECONDS);
@@ -405,5 +422,6 @@ public class JedisConnectionIntegrationTests extends AbstractConnectionIntegrati
conn.close();
// Make sure we don't end up with broken connection
factory2.getConnection().dbSize();
factory2.destroy();
}
}

View File

@@ -15,10 +15,6 @@
*/
package org.springframework.data.redis.connection.jredis;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.fail;
import org.apache.commons.pool.impl.GenericObjectPool.Config;
import org.jredis.JRedis;
import org.jredis.RedisException;
@@ -31,10 +27,13 @@ import org.junit.Test;
import org.springframework.data.redis.SettingsUtils;
import org.springframework.data.redis.connection.PoolException;
import static org.junit.Assert.*;
/**
* Integration test of {@link JredisPool}
*
* @author Jennifer Hickey
* @author Thomas Darimont
*
*/
public class JredisPoolTests {

View File

@@ -15,21 +15,22 @@
*/
package org.springframework.data.redis.connection.lettuce;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import com.lambdaworks.redis.RedisAsyncConnection;
import com.lambdaworks.redis.RedisClient;
import com.lambdaworks.redis.RedisConnection;
import com.lambdaworks.redis.RedisException;
import com.lambdaworks.redis.pubsub.RedisPubSubConnection;
import org.junit.After;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
/**
* Integration test of {@link AuthenticatingRedisClient}. Enable requirepass and
* comment out the @Ignore to run.
*
* @author Jennifer Hickey
* @author Thomas Darimont
*
*/
@Ignore("Redis must have requirepass set to run this test")
@@ -42,14 +43,27 @@ public class AuthenticatingRedisClientTests {
client = new AuthenticatingRedisClient("localhost", "foo");
}
@After
public void tearDown(){
if(client != null){
client.shutdown();
}
}
@Test
public void connect() {
RedisConnection<String, String> conn = client.connect();
conn.ping();
conn.close();
}
@Test(expected = RedisException.class)
public void connectWithInvalidPassword() {
if(client != null){
client.shutdown();
}
RedisClient badClient = new AuthenticatingRedisClient("localhost", "notthepassword");
badClient.connect();
}
@@ -58,30 +72,35 @@ public class AuthenticatingRedisClientTests {
public void codecConnect() {
RedisConnection<byte[], byte[]> conn = client.connect(LettuceConnection.CODEC);
conn.ping();
conn.close();
}
@Test
public void connectAsync() {
RedisAsyncConnection<String, String> conn = client.connectAsync();
conn.ping();
conn.close();
}
@Test
public void codecConnectAsync() {
RedisAsyncConnection<byte[], byte[]> conn = client.connectAsync(LettuceConnection.CODEC);
conn.ping();
conn.close();
}
@Test
public void connectPubSub() {
RedisPubSubConnection<String, String> conn = client.connectPubSub();
conn.ping();
conn.close();
}
@Test
public void codecConnectPubSub() {
RedisPubSubConnection<byte[], byte[]> conn = client.connectPubSub(LettuceConnection.CODEC);
conn.ping();
conn.close();
}
}

View File

@@ -15,10 +15,8 @@
*/
package org.springframework.data.redis.connection.lettuce;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.fail;
import com.lambdaworks.redis.RedisAsyncConnection;
import com.lambdaworks.redis.RedisException;
import org.apache.commons.pool.impl.GenericObjectPool.Config;
import org.junit.After;
import org.junit.Ignore;
@@ -27,22 +25,28 @@ import org.springframework.data.redis.SettingsUtils;
import org.springframework.data.redis.connection.PoolConfig;
import org.springframework.data.redis.connection.PoolException;
import com.lambdaworks.redis.RedisAsyncConnection;
import com.lambdaworks.redis.RedisException;
import static org.junit.Assert.*;
/**
* Unit test of {@link DefaultLettucePool}
*
* @author Jennifer Hickey
* @author Thomas Darimont
*
*/
public class DefaultLettucePoolTests {
public class
DefaultLettucePoolTests {
private DefaultLettucePool pool;
@After
public void tearDown() {
if(this.pool != null) {
if(this.pool.getClient() != null){
this.pool.getClient().shutdown();
}
this.pool.destroy();
}
}
@@ -54,6 +58,7 @@ public class DefaultLettucePoolTests {
RedisAsyncConnection<byte[], byte[]> client = pool.getResource();
assertNotNull(client);
client.ping();
client.close();
}
@Test
@@ -69,7 +74,9 @@ public class DefaultLettucePoolTests {
pool.getResource();
fail("PoolException should be thrown when pool exhausted");
} catch (PoolException e) {
}
}finally{
client.close();
}
}
@Test
@@ -80,6 +87,7 @@ public class DefaultLettucePoolTests {
pool.afterPropertiesSet();
RedisAsyncConnection<byte[], byte[]> client = pool.getResource();
assertNotNull(client);
client.close();
}
@Test(expected = PoolException.class)
@@ -100,6 +108,7 @@ public class DefaultLettucePoolTests {
assertNotNull(client);
pool.returnResource(client);
assertNotNull(pool.getResource());
client.close();
}
@Test
@@ -118,7 +127,10 @@ public class DefaultLettucePoolTests {
client.ping();
fail("Broken resouce connection should be closed");
} catch (RedisException e) {
}
} finally{
client.close();
client2.close();
}
}
@Test
@@ -153,6 +165,7 @@ public class DefaultLettucePoolTests {
pool.afterPropertiesSet();
RedisAsyncConnection<byte[], byte[]> conn = pool.getResource();
conn.ping();
conn.close();
}
@Ignore("Redis must have requirepass set to run this test")

View File

@@ -15,13 +15,8 @@
*/
package org.springframework.data.redis.connection.lettuce;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import com.lambdaworks.redis.RedisAsyncConnection;
import com.lambdaworks.redis.RedisException;
import org.junit.After;
import org.junit.Before;
import org.junit.Ignore;
@@ -33,13 +28,13 @@ import org.springframework.data.redis.connection.DefaultStringRedisConnection;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.StringRedisConnection;
import com.lambdaworks.redis.RedisAsyncConnection;
import com.lambdaworks.redis.RedisException;
import static org.junit.Assert.*;
/**
* Integration test of {@link LettuceConnectionFactory}
*
* @author Jennifer Hickey
* @author Thomas Darimont
*
*/
public class LettuceConnectionFactoryTests {
@@ -58,6 +53,10 @@ public class LettuceConnectionFactoryTests {
@After
public void tearDown() {
factory.destroy();
if(connection != null){
connection.close();
}
}
@SuppressWarnings("rawtypes")
@@ -81,6 +80,7 @@ public class LettuceConnectionFactoryTests {
assertNotSame(nativeConn, conn2.getNativeConnection());
conn2.set("anotherkey", "anothervalue");
assertEquals("anothervalue", conn2.get("anotherkey"));
conn2.close();
}
@SuppressWarnings("rawtypes")
@@ -97,7 +97,9 @@ public class LettuceConnectionFactoryTests {
fail("Expected exception using natively closed conn");
} catch (RedisSystemException e) {
// expected, as we are re-using the natively closed conn
}
}finally{
conn2.close();
}
}
@Test
@@ -122,6 +124,7 @@ public class LettuceConnectionFactoryTests {
// there should still be nothing in database 1
assertEquals(Long.valueOf(0), connection2.dbSize());
} finally {
connection2.close();
factory2.destroy();
}
}
@@ -153,6 +156,7 @@ public class LettuceConnectionFactoryTests {
.getNativeConnection();
factory.resetConnection();
assertNotSame(nativeConn, factory.getConnection().getNativeConnection());
nativeConn.close();
}
@SuppressWarnings("unchecked")
@@ -161,7 +165,9 @@ public class LettuceConnectionFactoryTests {
RedisAsyncConnection<byte[], byte[]> nativeConn = (RedisAsyncConnection<byte[], byte[]>) connection
.getNativeConnection();
factory.initConnection();
assertNotSame(nativeConn, factory.getConnection().getNativeConnection());
RedisConnection newConnection = factory.getConnection();
assertNotSame(nativeConn, newConnection.getNativeConnection());
newConnection.close();
}
@SuppressWarnings("unchecked")
@@ -171,7 +177,9 @@ public class LettuceConnectionFactoryTests {
.getNativeConnection();
factory.resetConnection();
factory.initConnection();
assertNotSame(nativeConn, factory.getConnection().getNativeConnection());
RedisConnection newConnection = factory.getConnection();
assertNotSame(nativeConn, newConnection.getNativeConnection());
newConnection.close();
}
public void testGetConnectionException() {
@@ -207,7 +215,10 @@ public class LettuceConnectionFactoryTests {
pool.afterPropertiesSet();
LettuceConnectionFactory factory2 = new LettuceConnectionFactory(pool);
factory2.afterPropertiesSet();
factory2.getConnection();
RedisConnection conn2 = factory2.getConnection();
conn2.close();
factory2.destroy();
pool.destroy();
}
@Ignore("Uncomment this test to manually check connection reuse in a pool scenario")
@@ -238,5 +249,6 @@ public class LettuceConnectionFactoryTests {
// Test shared and dedicated conns
conn.ping();
conn.bLPop(1, "key".getBytes());
conn.close();
}
}

View File

@@ -16,15 +16,7 @@
package org.springframework.data.redis.connection.lettuce;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.junit.Assume.assumeTrue;
import static org.springframework.data.redis.SpinBarrier.waitFor;
import java.util.Arrays;
import java.util.concurrent.atomic.AtomicBoolean;
import com.lambdaworks.redis.RedisAsyncConnection;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.dao.DataAccessException;
@@ -42,13 +34,19 @@ import org.springframework.test.annotation.IfProfileValue;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import com.lambdaworks.redis.RedisAsyncConnection;
import java.util.Arrays;
import java.util.concurrent.atomic.AtomicBoolean;
import static org.junit.Assert.*;
import static org.junit.Assume.assumeTrue;
import static org.springframework.data.redis.SpinBarrier.waitFor;
/**
* Integration test of {@link LettuceConnection}
*
* @author Costin Leau
* @author Jennifer Hickey
* @author Thomas Darimont
*
*/
@RunWith(SpringJUnit4ClassRunner.class)
@@ -94,6 +92,8 @@ public class LettuceConnectionIntegrationTests extends AbstractConnectionIntegra
// Now it should be set
assertEquals("delay", conn2.get("txs1"));
conn2.closePipeline();
conn2.close();
}
@Test
@@ -141,7 +141,9 @@ public class LettuceConnectionIntegrationTests extends AbstractConnectionIntegra
// The dedicated connection should not be closed b/c it's part of a pool
connection.multi();
connection.close();
factory2.destroy();
pool.destroy();
}
@Test
@@ -158,7 +160,10 @@ public class LettuceConnectionIntegrationTests extends AbstractConnectionIntegra
connection.close();
// The dedicated connection should not be closed
connection.ping();
connection.close();
factory2.destroy();
pool.destroy();
}
@Test
@@ -199,6 +204,7 @@ public class LettuceConnectionIntegrationTests extends AbstractConnectionIntegra
}
connection.close();
factory2.destroy();
pool.destroy();
}
@Test
@@ -211,7 +217,9 @@ public class LettuceConnectionIntegrationTests extends AbstractConnectionIntegra
factory2.afterPropertiesSet();
RedisConnection connection = factory2.getConnection();
connection.select(2);
connection.close();
factory2.destroy();
pool.destroy();
}
@Test(expected = UnsupportedOperationException.class)
@@ -253,6 +261,7 @@ public class LettuceConnectionIntegrationTests extends AbstractConnectionIntegra
scriptDead.set(true);
}
conn2.close();
factory2.destroy();
}
});
th.start();
@@ -282,6 +291,7 @@ public class LettuceConnectionIntegrationTests extends AbstractConnectionIntegra
conn2.del("foo");
}
conn2.close();
factory2.destroy();
}
}
}

View File

@@ -15,14 +15,6 @@
*/
package org.springframework.data.redis.connection.lettuce;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeTrue;
import static org.springframework.data.redis.SpinBarrier.waitFor;
import java.util.Arrays;
import java.util.concurrent.atomic.AtomicBoolean;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.dao.DataAccessException;
@@ -38,10 +30,19 @@ import org.springframework.test.annotation.IfProfileValue;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import java.util.Arrays;
import java.util.concurrent.atomic.AtomicBoolean;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeTrue;
import static org.springframework.data.redis.SpinBarrier.waitFor;
/**
* Integration test of {@link LettuceConnection} pipeline functionality
*
* @author Jennifer Hickey
* @author Thomas Darimont
*
*/
@RunWith(SpringJUnit4ClassRunner.class)
@@ -94,6 +95,7 @@ public class LettuceConnectionPipelineIntegrationTests extends
scriptDead.set(true);
}
conn2.close();
factory2.destroy();
}
});
th.start();
@@ -124,6 +126,7 @@ public class LettuceConnectionPipelineIntegrationTests extends
conn2.del("foo");
}
conn2.close();
factory2.destroy();
}
}
}

View File

@@ -15,10 +15,6 @@
*/
package org.springframework.data.redis.connection.lettuce;
import static org.junit.Assert.assertEquals;
import java.util.Arrays;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -29,11 +25,16 @@ import org.springframework.test.annotation.IfProfileValue;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import java.util.Arrays;
import static org.junit.Assert.assertEquals;
/**
* Integration test of {@link LettuceConnection} functionality within a
* transaction
*
* @author Jennifer Hickey
* @author Thomas Darimont
*
*/
@RunWith(SpringJUnit4ClassRunner.class)
@@ -74,6 +75,7 @@ public class LettuceConnectionTransactionIntegrationTests extends
conn2.del("foo");
}
conn2.close();
factory2.destroy();
}
}

View File

@@ -15,11 +15,6 @@
*/
package org.springframework.data.redis.listener;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Test;
@@ -38,11 +33,17 @@ import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactor
import org.springframework.data.redis.connection.srp.SrpConnectionFactory;
import org.springframework.data.redis.listener.adapter.MessageListenerAdapter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
/**
* Integration tests confirming that {@link RedisMessageListenerContainer}
* closes connections after unsubscribing
*
* @author Jennifer Hickey
* @author Thomas Darimont
*
*/
@RunWith(Parameterized.class)