Skip to content

Commit

Permalink
Add ExpectNoConnectionLeak annotation
Browse files Browse the repository at this point in the history
  • Loading branch information
jeanbisutti committed Dec 27, 2020
1 parent cf17771 commit c0bd884
Show file tree
Hide file tree
Showing 14 changed files with 1,054 additions and 8 deletions.
8 changes: 8 additions & 0 deletions core/src/main/java/org/quickperf/measure/BooleanMeasure.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ public class BooleanMeasure extends AbstractComparablePerfMeasure<BooleanMeasure

private final String comment;

public static BooleanMeasure of(boolean value) {
return value ? BooleanMeasure.TRUE : BooleanMeasure.FALSE;
}

public BooleanMeasure(Boolean value) {
this.value = value;
this.comment = NO_COMMENT;
Expand All @@ -33,6 +37,10 @@ public Boolean getValue() {
return value;
}

public boolean isTrue() {
return this == TRUE || value.equals(Boolean.TRUE);
}

@Override
public Void getUnit() {
return null;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
*
* Copyright 2019-2020 the original author or authors.
*/

package org.quickperf.repository;

import org.quickperf.WorkingFolder;
import org.quickperf.measure.BooleanMeasure;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

public class BooleanMeasureRepository {

public static final BooleanMeasureRepository INSTANCE = new BooleanMeasureRepository();

private BooleanMeasureRepository() { }

private final ObjectInputStreamBuilder objectInputStreamBuilder = ObjectInputStreamBuilder.INSTANCE;

private final ObjectOutputStreamBuilder objectOutputStreamBuilder = ObjectOutputStreamBuilder.INSTANCE;

public void save(BooleanMeasure booleanMeasure, WorkingFolder workingFolder, String fileName) {
String workingFolderPath = workingFolder.getPath();
try(ObjectOutputStream objectOutputStream = objectOutputStreamBuilder
.build(workingFolderPath, fileName)
) {
objectOutputStream.writeBoolean(booleanMeasure.getValue());
objectOutputStream.flush();
} catch (IOException ioe) {
throw new IllegalStateException("Unable to save " + fileName, ioe);
}
}

public BooleanMeasure find(WorkingFolder workingFolder, String fileName) {
boolean booleanValue = retrieveBooleanValueFromFile(workingFolder, fileName);
return BooleanMeasure.of(booleanValue);
}

private boolean retrieveBooleanValueFromFile(WorkingFolder workingFolder, String fileName) {
String workingFolderPath = workingFolder.getPath();
try(ObjectInputStream objectInputStream = objectInputStreamBuilder
.buildObjectInputStream(workingFolderPath, fileName)
) {
return objectInputStream.readBoolean();
} catch (IOException ioe) {
throw new IllegalStateException("Unable to deserialize.", ioe);
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
*
* Copyright 2019-2020 the original author or authors.
*/

package org.quickperf.sql.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* <p><code>ExpectNoConnectionLeak</code> annotation allows detecting database connection leaks.
*
* <p>This annotation verifies that each connection got from the data source is well closed during the test method execution.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE})
public @interface ExpectNoConnectionLeak {

}
Original file line number Diff line number Diff line change
Expand Up @@ -382,4 +382,16 @@ public int value() {
};
}

/**
*Allows to build {@link org.quickperf.sql.annotation.ExpectNoConnectionLeak} annotation.
*/
public static ExpectNoConnectionLeak expectNoConnectionLeak() {
return new ExpectNoConnectionLeak() {
@Override
public Class<? extends Annotation> annotationType() {
return ExpectNoConnectionLeak.class;
}
};
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

import net.ttddyy.dsproxy.listener.ChainListener;
import net.ttddyy.dsproxy.support.ProxyDataSource;
import org.quickperf.sql.config.library.QuickPerfProxyDataSource;

import javax.sql.DataSource;

Expand All @@ -26,7 +27,7 @@ private QuickPerfSqlDataSourceBuilder() {}

public ProxyDataSource buildProxy(DataSource dataSource) {

ProxyDataSource proxyDataSource = new ProxyDataSource();
QuickPerfProxyDataSource proxyDataSource = new QuickPerfProxyDataSource();

ChainListener chainListener = new ChainListener();

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
*
* Copyright 2019-2020 the original author or authors.
*/

package org.quickperf.sql.config.library;

import net.ttddyy.dsproxy.support.ProxyDataSource;
import org.quickperf.sql.connection.QuickPerfDatabaseConnection;

import java.sql.Connection;
import java.sql.SQLException;

public class QuickPerfProxyDataSource extends ProxyDataSource {

@Override
public Connection getConnection() throws SQLException {
Connection connection = super.getConnection();
QuickPerfDatabaseConnection connectionProxy = QuickPerfDatabaseConnection.buildFrom(connection);
connectionProxy.getFromTheDatasource();
return connectionProxy;
}

@Override
public Connection getConnection(String username, String password) throws SQLException {
Connection connection = super.getConnection(username, password);
QuickPerfDatabaseConnection connectionProxy = QuickPerfDatabaseConnection.buildFrom(connection);
connectionProxy.getFromTheDatasource();
return connectionProxy;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
import org.quickperf.sql.batch.SqlStatementBatchVerifier;
import org.quickperf.sql.bindparams.AllParametersAreBoundExtractor;
import org.quickperf.sql.bindparams.DisableQueriesWithoutBindParametersVerifier;
import org.quickperf.sql.connection.ConnectionLeakListener;
import org.quickperf.sql.connection.ConnectionLeakVerifier;
import org.quickperf.sql.delete.DeleteCountMeasureExtractor;
import org.quickperf.sql.delete.MaxOfDeletesPerfIssueVerifier;
import org.quickperf.sql.delete.NumberOfSqlDeletePerfIssueVerifier;
Expand Down Expand Up @@ -203,10 +205,15 @@ private SqlAnnotationsConfigs() { }
.perfIssueVerifier(DisableQueriesWithoutBindParametersVerifier.INSTANCE)
.build(DisableQueriesWithoutBindParameters.class);

static final AnnotationConfig MAX_SQL_DELETE = new AnnotationConfig.Builder()
.perfRecorderClass(PersistenceSqlRecorder.class)
.perfMeasureExtractor(DeleteCountMeasureExtractor.INSTANCE)
.perfIssueVerifier(MaxOfDeletesPerfIssueVerifier.INSTANCE)
.build(ExpectMaxDelete.class);
static final AnnotationConfig MAX_SQL_DELETE = new AnnotationConfig.Builder()
.perfRecorderClass(PersistenceSqlRecorder.class)
.perfMeasureExtractor(DeleteCountMeasureExtractor.INSTANCE)
.perfIssueVerifier(MaxOfDeletesPerfIssueVerifier.INSTANCE)
.build(ExpectMaxDelete.class);

static final AnnotationConfig EXPECT_NO_CONNECTION_LEAK = new AnnotationConfig.Builder()
.perfRecorderClass(ConnectionLeakListener.class)
.perfIssueVerifier(ConnectionLeakVerifier.INSTANCE)
.build(ExpectNoConnectionLeak.class);

}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import org.quickperf.config.library.QuickPerfConfigLoader;
import org.quickperf.sql.PersistenceSqlRecorder;
import org.quickperf.sql.batch.SqlStatementBatchRecorder;
import org.quickperf.sql.connection.ConnectionLeakListener;
import org.quickperf.sql.display.DisplaySqlOfTestMethodBodyRecorder;
import org.quickperf.sql.display.DisplaySqlRecorder;

Expand Down Expand Up @@ -55,13 +56,15 @@ public Collection<AnnotationConfig> loadAnnotationConfigs() {
, SqlAnnotationsConfigs.DISABLE_QUERIES_WITHOUT_BIND_PARAMETERS
, SqlAnnotationsConfigs.DISABLE_STATEMENTS
, SqlAnnotationsConfigs.ENABLE_STATEMENTS
, SqlAnnotationsConfigs.EXPECT_NO_CONNECTION_LEAK
);
}

@Override
public Collection<RecorderExecutionOrder> loadRecorderExecutionOrdersBeforeTestMethod() {
return Arrays.asList(
new RecorderExecutionOrder(PersistenceSqlRecorder.class, 2000)
new RecorderExecutionOrder(ConnectionLeakListener.class, 1999)
, new RecorderExecutionOrder(PersistenceSqlRecorder.class, 2000)
, new RecorderExecutionOrder(DisplaySqlRecorder.class, 2001)
, new RecorderExecutionOrder(DisplaySqlOfTestMethodBodyRecorder.class, 2002)
, new RecorderExecutionOrder(SqlStatementBatchRecorder.class, 2003)
Expand All @@ -72,7 +75,8 @@ public Collection<RecorderExecutionOrder> loadRecorderExecutionOrdersBeforeTestM
@Override
public Collection<RecorderExecutionOrder> loadRecorderExecutionOrdersAfterTestMethod() {
return Arrays.asList(
new RecorderExecutionOrder(PersistenceSqlRecorder.class, 7000)
new RecorderExecutionOrder(ConnectionLeakListener.class, 6999)
, new RecorderExecutionOrder(PersistenceSqlRecorder.class, 7000)
, new RecorderExecutionOrder(DisplaySqlRecorder.class, 7001)
, new RecorderExecutionOrder(DisplaySqlOfTestMethodBodyRecorder.class, 7002)
, new RecorderExecutionOrder(SqlStatementBatchRecorder.class, 7003)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
*
* Copyright 2019-2020 the original author or authors.
*/

package org.quickperf.sql.connection;

import org.quickperf.TestExecutionContext;
import org.quickperf.WorkingFolder;
import org.quickperf.measure.BooleanMeasure;
import org.quickperf.repository.BooleanMeasureRepository;

import java.sql.Connection;
import java.util.ArrayList;
import java.util.List;

public class ConnectionLeakListener extends ConnectionsListener<BooleanMeasure> {

private final List<Connection> connections = new ArrayList<>();

private BooleanMeasure connectionLeak;

private static final String CONNECTION_LEAK_FILE_NAME = "connection-leak.ser";

@Override
public void getFromTheDataSource(Connection connection) {
connections.add(connection);
}

@Override
public void close(Connection connection) {
connections.remove(connection);
}

@Override
public void startRecording(TestExecutionContext testExecutionContext) {
connections.clear();
ConnectionListenerRegistry.INSTANCE.register(this);
}

@Override
public void stopRecording(TestExecutionContext testExecutionContext) {
ConnectionListenerRegistry.unregister(this);
connectionLeak = BooleanMeasure.of(!connections.isEmpty());
connections.clear();
if (testExecutionContext.testExecutionUsesTwoJVMs()) {
WorkingFolder workingFolder = testExecutionContext.getWorkingFolder();
BooleanMeasureRepository.INSTANCE.save(connectionLeak, workingFolder, CONNECTION_LEAK_FILE_NAME);
}
}

@Override
public BooleanMeasure findRecord(TestExecutionContext testExecutionContext) {
if (testExecutionContext.testExecutionUsesTwoJVMs()) {
WorkingFolder workingFolder = testExecutionContext.getWorkingFolder();
return BooleanMeasureRepository.INSTANCE.find(workingFolder, CONNECTION_LEAK_FILE_NAME);
}
return connectionLeak;
}

@Override
public void cleanResources() { }

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
*
* Copyright 2019-2020 the original author or authors.
*/

package org.quickperf.sql.connection;

import org.quickperf.issue.PerfIssue;
import org.quickperf.issue.VerifiablePerformanceIssue;
import org.quickperf.measure.BooleanMeasure;
import org.quickperf.sql.annotation.ExpectNoConnectionLeak;

public class ConnectionLeakVerifier implements VerifiablePerformanceIssue<ExpectNoConnectionLeak, BooleanMeasure> {

public static final ConnectionLeakVerifier INSTANCE = new ConnectionLeakVerifier();

private ConnectionLeakVerifier() { }

@Override
public PerfIssue verifyPerfIssue(ExpectNoConnectionLeak annotation, BooleanMeasure connectionLeak) {
if(connectionLeak.isTrue()) {
return new PerfIssue("Database connection leak");
}
return PerfIssue.NONE;
}

}
Loading

0 comments on commit c0bd884

Please sign in to comment.