Skip to content

Commit c820edd

Browse files
Merge pull request #120 from utPLSQL/feature/issue-110-code-coverage-assets
#110 - Use local assets for code coverage report for offline usage
2 parents 7fb1947 + 31464af commit c820edd

31 files changed

+2640
-25
lines changed

sqldev/src/main/java/org/utplsql/sqldev/coverage/CodeCoverageReporter.java

+76-13
Original file line numberDiff line numberDiff line change
@@ -17,17 +17,25 @@
1717

1818
import java.awt.Desktop;
1919
import java.io.File;
20+
import java.io.IOException;
21+
import java.io.InputStream;
22+
import java.net.MalformedURLException;
2023
import java.net.URL;
2124
import java.nio.charset.StandardCharsets;
25+
import java.nio.file.Files;
26+
import java.nio.file.Path;
27+
import java.nio.file.Paths;
28+
import java.nio.file.StandardCopyOption;
2229
import java.sql.Connection;
23-
import java.util.ArrayList;
24-
import java.util.Arrays;
25-
import java.util.HashMap;
26-
import java.util.List;
27-
import java.util.Map;
30+
import java.util.*;
31+
import java.util.jar.JarEntry;
32+
import java.util.jar.JarFile;
2833
import java.util.logging.Logger;
2934
import java.util.stream.Collectors;
3035

36+
import org.springframework.core.io.Resource;
37+
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
38+
import org.springframework.core.io.support.ResourcePatternResolver;
3139
import org.utplsql.sqldev.dal.RealtimeReporterDao;
3240
import org.utplsql.sqldev.dal.UtplsqlDao;
3341
import org.utplsql.sqldev.exception.GenericDatabaseAccessException;
@@ -42,22 +50,25 @@
4250

4351
public class CodeCoverageReporter {
4452
private static final Logger logger = Logger.getLogger(CodeCoverageReporter.class.getName());
53+
private static final String ASSETS_PATH = "coverage/assets/";
4554

4655
private String connectionName;
4756
private Connection conn;
48-
private List<String> pathList;
49-
private List<String> includeObjectList;
57+
private final List<String> pathList;
58+
private final List<String> includeObjectList;
5059
private CodeCoverageReporterDialog frame;
5160
private String schemas;
5261
private String includeObjects;
5362
private String excludeObjects;
63+
private Path assetDir;
5464

5565
public CodeCoverageReporter(final List<String> pathList, final List<String> includeObjectList,
5666
final String connectionName) {
5767
this.pathList = pathList;
5868
this.includeObjectList = includeObjectList;
5969
setDefaultSchema();
6070
setConnection(connectionName);
71+
setAssetDir();
6172
}
6273

6374
// constructor for testing purposes only
@@ -67,6 +78,7 @@ public CodeCoverageReporter(final List<String> pathList, final List<String> incl
6778
this.includeObjectList = includeObjectList;
6879
this.conn = conn;
6980
setDefaultSchema();
81+
setAssetDir();
7082
}
7183

7284
private void setConnection(final String connectionName) {
@@ -105,6 +117,59 @@ private void setDefaultSchema() {
105117
}
106118
}
107119

120+
private void setAssetDir() {
121+
try {
122+
assetDir = Files.createTempDirectory("utplsql_assets_");
123+
} catch (IOException e) {
124+
throw new GenericRuntimeException("Cannot create temporary directory for code coverage report assets.", e);
125+
}
126+
populateCoverageAssets();
127+
}
128+
129+
// public for testing purposes only
130+
public URL getHtmlReportAssetPath() {
131+
try {
132+
return Paths.get(assetDir.toString()).toUri().toURL();
133+
} catch (MalformedURLException e) {
134+
throw new GenericRuntimeException("Cannot convert code coverage asset path to URL.", e);
135+
}
136+
}
137+
138+
private void copyStreamToFile(InputStream inputStream, Path file) throws IOException {
139+
file.toFile().mkdirs();
140+
Files.copy(inputStream, file, StandardCopyOption.REPLACE_EXISTING);
141+
}
142+
143+
private void populateCoverageAssets() {
144+
logger.fine(() -> "Copying code coverage report assets to " + assetDir.toString() + "...");
145+
try {
146+
final File file = new File(getClass().getProtectionDomain().getCodeSource().getLocation().getPath());
147+
if (file.isFile()) {
148+
// class loaded from a JAR file
149+
final JarFile jar = new JarFile(file);
150+
final List<JarEntry> entries = jar.stream().filter(entry -> !entry.isDirectory() && entry.getName().startsWith(ASSETS_PATH)).collect(Collectors.toList());
151+
for (JarEntry entry : entries) {
152+
Path f = Paths.get(assetDir.toString() + File.separator + entry.getName().substring(ASSETS_PATH.length()));
153+
copyStreamToFile(jar.getInputStream(entry), f);
154+
}
155+
} else {
156+
// class loaded from file system (IDE or during test/build)
157+
ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
158+
Resource[] resources = resolver.getResources(ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + "/" + ASSETS_PATH + "**");
159+
for (Resource resource : resources) {
160+
if (Objects.requireNonNull(resource.getFilename()).contains(".")) {
161+
// process files but not directories, assume that directories do not contain a period
162+
String path = resource.getURL().getPath();
163+
Path f = Paths.get(assetDir.toString() + File.separator + path.substring(path.lastIndexOf(ASSETS_PATH) + ASSETS_PATH.length()));
164+
copyStreamToFile(resource.getInputStream(), f);
165+
}
166+
}
167+
}
168+
} catch (IOException e) {
169+
throw new GenericRuntimeException("Error while copying coverage report assets to temporary directory.", e);
170+
}
171+
}
172+
108173
private ArrayList<String> toStringList(final String s) {
109174
final ArrayList<String> list = new ArrayList<>();
110175
if (s != null && !s.isEmpty()) {
@@ -142,7 +207,7 @@ private void run() {
142207

143208
private void runCodeCoverageWithRealtimeReporter() {
144209
final UtplsqlRunner runner = new UtplsqlRunner(pathList, toStringList(schemas), toStringList(includeObjects),
145-
toStringList(excludeObjects), connectionName);
210+
toStringList(excludeObjects), getHtmlReportAssetPath(), connectionName);
146211
runner.runTestAsync();
147212
}
148213

@@ -152,7 +217,7 @@ private void runCodeCoverageStandalone() {
152217
coverageConn = conn != null ? conn : DatabaseTools.cloneConnection(connectionName);
153218
final UtplsqlDao dao = new UtplsqlDao(coverageConn);
154219
final String html = dao.htmlCodeCoverage(pathList, toStringList(schemas),
155-
toStringList(includeObjects), toStringList(excludeObjects));
220+
toStringList(includeObjects), toStringList(excludeObjects), getHtmlReportAssetPath());
156221
openInBrowser(html);
157222
} finally {
158223
try {
@@ -174,7 +239,7 @@ public static void openInBrowser(String html) {
174239
final URL url = file.toURI().toURL();
175240
logger.fine(() -> "Opening " + url.toExternalForm() + " in browser...");
176241
final Desktop desktop = Desktop.isDesktopSupported() ? Desktop.getDesktop() : null;
177-
if (desktop != null && desktop.isSupported(Desktop.Action.BROWSE) && url != null) {
242+
if (desktop != null && desktop.isSupported(Desktop.Action.BROWSE)) {
178243
desktop.browse(url.toURI());
179244
logger.fine(() -> url.toExternalForm() + " opened in browser.");
180245
} else {
@@ -229,9 +294,7 @@ public void setExcludeObjects(final String excludeObjects) {
229294
}
230295

231296
public Thread runAsync() {
232-
final Thread thread = new Thread(() -> {
233-
run();
234-
});
297+
final Thread thread = new Thread(this::run);
235298
thread.setName("code coverage reporter");
236299
thread.start();
237300
return thread;

sqldev/src/main/java/org/utplsql/sqldev/dal/RealtimeReporterDao.java

+4-3
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
import java.io.IOException;
1919
import java.io.StringReader;
20+
import java.net.URL;
2021
import java.sql.Clob;
2122
import java.sql.Connection;
2223
import java.sql.ResultSet;
@@ -108,11 +109,11 @@ public void produceReport(final String reporterId, final List<String> pathList)
108109

109110
public void produceReportWithCoverage(final String realtimeReporterId, final String coverageReporterId,
110111
final List<String> pathList, final List<String> schemaList, final List<String> includeObjectList,
111-
final List<String> excludeObjectList) {
112+
final List<String> excludeObjectList, final URL htmlReportAssetPath) {
112113
StringBuilder sb = new StringBuilder();
113114
sb.append("DECLARE\n");
114115
sb.append(" l_rt_rep ut_realtime_reporter := ut_realtime_reporter();\n");
115-
sb.append(" l_cov_rep ut_coverage_html_reporter := ut_coverage_html_reporter();\n");
116+
sb.append(" l_cov_rep ut_coverage_html_reporter := ut_coverage_html_reporter(a_html_report_assets_path => ?);\n");
116117
sb.append("BEGIN\n");
117118
sb.append(" l_rt_rep.set_reporter_id(?);\n");
118119
sb.append(" l_rt_rep.output_buffer.init();\n");
@@ -143,7 +144,7 @@ public void produceReportWithCoverage(final String realtimeReporterId, final Str
143144
sb.append(" sys.dbms_output.disable;\n");
144145
sb.append("END;");
145146
final String plsql = sb.toString();
146-
final Object[] binds = { realtimeReporterId, coverageReporterId };
147+
final Object[] binds = { htmlReportAssetPath == null ? null : htmlReportAssetPath.toExternalForm(), realtimeReporterId, coverageReporterId };
147148
jdbcTemplate.update(plsql, binds);
148149
}
149150

sqldev/src/main/java/org/utplsql/sqldev/dal/UtplsqlDao.java

+12-2
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
*/
1616
package org.utplsql.sqldev.dal;
1717

18+
import java.net.URL;
1819
import java.sql.CallableStatement;
1920
import java.sql.Connection;
2021
import java.sql.SQLException;
@@ -907,12 +908,14 @@ public OutputLines doInCallableStatement(final CallableStatement cs) throws SQLE
907908
* @param excludeObjectList
908909
* list of objects to be excluded from coverage analysis. None, if
909910
* empty
911+
* @param htmlReportAssetPath
912+
* path of the assets for the coverage report. Default, if null
910913
* @return HTML code coverage report in HTML format
911914
* @throws DataAccessException
912915
* if there is a problem
913916
*/
914917
public String htmlCodeCoverage(final List<String> pathList, final List<String> schemaList,
915-
final List<String> includeObjectList, final List<String> excludeObjectList) {
918+
final List<String> includeObjectList, final List<String> excludeObjectList, final URL htmlReportAssetPath) {
916919
StringBuilder sb = new StringBuilder();
917920
sb.append("SELECT column_value\n");
918921
sb.append(" FROM table(\n");
@@ -935,7 +938,14 @@ public String htmlCodeCoverage(final List<String> pathList, final List<String> s
935938
sb.append(StringTools.getCSV(excludeObjectList, 16));
936939
sb.append(" ),\n");
937940
}
938-
sb.append(" a_reporter => ut_coverage_html_reporter()\n");
941+
sb.append(" a_reporter => ut_coverage_html_reporter(\n");
942+
sb.append(" a_html_report_assets_path => '");
943+
if (htmlReportAssetPath != null) {
944+
// empty string is handled as NULL in Oracle Database
945+
sb.append(htmlReportAssetPath.toExternalForm());
946+
}
947+
sb.append("'\n");
948+
sb.append(" )\n");
939949
sb.append(" )\n");
940950
sb.append(" )");
941951
final String sql = sb.toString();

sqldev/src/main/java/org/utplsql/sqldev/runner/UtplsqlRunner.java

+12-5
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
import java.awt.Dimension;
1919
import java.awt.Toolkit;
20+
import java.net.URL;
2021
import java.sql.Connection;
2122
import java.text.SimpleDateFormat;
2223
import java.util.Date;
@@ -71,24 +72,28 @@ public class UtplsqlRunner implements RealtimeReporterEventConsumer {
7172
private Thread producerThread;
7273
private Thread consumerThread;
7374
private boolean debug = false;
75+
private final URL htmlReportAssetPath;
7476

7577
public UtplsqlRunner(final List<String> pathList, final String connectionName) {
7678
this.withCodeCoverage = false;
7779
this.pathList = pathList;
7880
this.schemaList = null;
7981
this.includeObjectList = null;
8082
this.excludeObjectList = null;
83+
this.htmlReportAssetPath = null;
8184
setConnection(connectionName);
8285
this.context = Context.newIdeContext();
8386
}
8487

8588
public UtplsqlRunner(final List<String> pathList, final List<String> schemaList,
86-
final List<String> includeObjectList, final List<String> excludeObjectList, final String connectionName) {
89+
final List<String> includeObjectList, final List<String> excludeObjectList,
90+
final URL htmlReportAssetPath, final String connectionName) {
8791
this.withCodeCoverage = true;
8892
this.pathList = pathList;
8993
this.schemaList = schemaList;
9094
this.includeObjectList = includeObjectList;
9195
this.excludeObjectList = excludeObjectList;
96+
this.htmlReportAssetPath = htmlReportAssetPath;
9297
setConnection(connectionName);
9398
this.context = Context.newIdeContext();
9499
}
@@ -102,21 +107,23 @@ public UtplsqlRunner(final List<String> pathList, final Connection producerConn,
102107
this.schemaList = null;
103108
this.includeObjectList = null;
104109
this.excludeObjectList = null;
110+
this.htmlReportAssetPath = null;
105111
this.producerConn = producerConn;
106112
this.consumerConn = consumerConn;
107113
}
108114

109115
/**
110-
* this constructor is intended for tests only (with code coverage)
116+
* this constructor is intended for tests only (with code coverage and default htmlReportAssetPath)
111117
*/
112118
public UtplsqlRunner(final List<String> pathList, final List<String> schemaList,
113-
final List<String> includeObjectList, final List<String> excludeObjectList, final Connection producerConn,
114-
final Connection consumerConn) {
119+
final List<String> includeObjectList, final List<String> excludeObjectList,
120+
final Connection producerConn, final Connection consumerConn) {
115121
this.withCodeCoverage = true;
116122
this.pathList = pathList;
117123
this.schemaList = schemaList;
118124
this.includeObjectList = includeObjectList;
119125
this.excludeObjectList = excludeObjectList;
126+
this.htmlReportAssetPath = null;
120127
this.producerConn = producerConn;
121128
this.consumerConn = consumerConn;
122129
}
@@ -314,7 +321,7 @@ private void produce() {
314321
logger.fine(() -> "Running utPLSQL tests and producing events via reporter id " + realtimeReporterId + "...");
315322
final RealtimeReporterDao dao = new RealtimeReporterDao(producerConn);
316323
if (withCodeCoverage) {
317-
dao.produceReportWithCoverage(realtimeReporterId, coverageReporterId, pathList, schemaList, includeObjectList, excludeObjectList);
324+
dao.produceReportWithCoverage(realtimeReporterId, coverageReporterId, pathList, schemaList, includeObjectList, excludeObjectList, htmlReportAssetPath);
318325
} else {
319326
if (!debug) {
320327
dao.produceReport(realtimeReporterId, pathList);

0 commit comments

Comments
 (0)