• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In
Build has been canceled!

future-architect / uroborosql / #769

12 Sep 2024 02:26PM UTC coverage: 90.35% (-0.9%) from 91.21%
#769

Pull #332

HidekiSugimoto189
Refactoring log-related class and method names.
Pull Request #332: Enable per-SQL-ID log suppression (#322)

384 of 587 new or added lines in 32 files covered. (65.42%)

9 existing lines in 7 files now uncovered.

8885 of 9834 relevant lines covered (90.35%)

0.9 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

93.09
/src/main/java/jp/co/future/uroborosql/coverage/reports/html/HtmlReportCoverageHandler.java
1
/**
2
 * Copyright (c) 2017-present, Future Corporation
3
 *
4
 * This source code is licensed under the MIT license found in the
5
 * LICENSE file in the root directory of this source tree.
6
 */
7
package jp.co.future.uroborosql.coverage.reports.html;
8

9
import java.io.BufferedWriter;
10
import java.io.IOException;
11
import java.nio.charset.StandardCharsets;
12
import java.nio.file.Files;
13
import java.nio.file.Path;
14
import java.nio.file.Paths;
15
import java.nio.file.StandardCopyOption;
16
import java.time.ZonedDateTime;
17
import java.time.format.DateTimeFormatter;
18
import java.util.Collection;
19
import java.util.Comparator;
20
import java.util.Map;
21
import java.util.concurrent.ConcurrentHashMap;
22
import java.util.stream.Collectors;
23
import java.util.stream.Stream;
24

25
import jp.co.future.uroborosql.SqlAgent;
26
import jp.co.future.uroborosql.coverage.CoverageData;
27
import jp.co.future.uroborosql.coverage.CoverageHandler;
28
import jp.co.future.uroborosql.log.support.CoverageLoggingSupport;
29
import jp.co.future.uroborosql.utils.ObjectUtils;
30

31
/**
32
 * カバレッジレポート出力ハンドラ<br>
33
 * htmlのカバレッジレポートを出力する
34
 *
35
 * <pre>
36
 * system property "uroborosql.sql.coverage" に "jp.co.future.uroborosql.coverage.reports.html.HtmlReportCoverageHandler" を指定することで
37
 * 本機能を利用することができます。
38
 * </pre>
39
 *
40
 * @author ota
41
 */
42
public class HtmlReportCoverageHandler implements CoverageHandler, CoverageLoggingSupport {
43
        /** カバレッジ情報. */
44
        private final Map<String, Map<String, SqlCoverageReport>> coverages = new ConcurrentHashMap<>();
1✔
45

46
        /** レポートディレクトリパス. */
47
        private final Path reportDirPath;
48

49
        /**
50
         * コンストラクタ<br>
51
         *
52
         * <pre>
53
         * system property "uroborosql.sql.coverage.dir" が指定された場合、指定されたPATHに レポートを出力します。
54
         * 指定の無い場合、デフォルトで "./target/coverage/sql" に レポートを出力します。
55
         * </pre>
56
         */
57
        public HtmlReportCoverageHandler() {
1✔
58
                var s = System.getProperty(SqlAgent.KEY_SQL_COVERAGE + ".dir");
1✔
59
                if (ObjectUtils.isNotEmpty(s)) {
1✔
60
                        this.reportDirPath = Paths.get(s);
1✔
61
                } else {
62
                        this.reportDirPath = Paths.get("target", "coverage", "sql");
×
63
                }
64
        }
1✔
65

66
        /**
67
         * コンストラクタ
68
         *
69
         * @param reportDirPath レポートファイルPATH
70
         */
71
        public HtmlReportCoverageHandler(final Path reportDirPath) {
×
72
                this.reportDirPath = reportDirPath;
×
73
        }
×
74

75
        @Override
76
        public synchronized void accept(final CoverageData coverageData) {
77
                if (ObjectUtils.isEmpty(coverageData.getSqlName())) {
1✔
78
                        // SQL名の設定されていないSQLは集約しない
79
                        return;
×
80
                }
81
                var map = coverages.computeIfAbsent(coverageData.getSqlName(), k -> new ConcurrentHashMap<>());
1✔
82
                var sqlCoverage = map.computeIfAbsent(coverageData.getMd5(),
1✔
83
                                k -> new SqlCoverageReport(coverageData.getSqlName(),
1✔
84
                                                coverageData.getSql(),
1✔
85
                                                coverageData.getMd5(),
1✔
86
                                                this.reportDirPath,
87
                                                map.size()));
1✔
88

89
                sqlCoverage.accept(coverageData.getPassRoute());
1✔
90
        }
1✔
91

92
        @Override
93
        public synchronized void onSqlAgentClose() {
94
                coverages.values().stream()
1✔
95
                                .map(Map::values)
1✔
96
                                .flatMap(Collection::stream)
1✔
97
                                .forEach(SqlCoverageReport::writeHtml);
1✔
98
                writeHtml();
1✔
99
        }
1✔
100

101
        private void writeHtml() {
102
                try {
103
                        Files.createDirectories(this.reportDirPath);
1✔
104
                        // copy resources
105
                        var packagePath = this.getClass().getPackage().getName().replace(".", "/");
1✔
106
                        Stream.of("style.css", "jquery-3.2.0.min.js", "stupidtable.min.js", "highlight.pack.js", "sqlcov.js",
1✔
107
                                        "icon.png").forEach(filename -> {
1✔
108
                                                try (var src = this.getClass().getClassLoader()
1✔
109
                                                                .getResourceAsStream(packagePath + "/" + filename)) {
1✔
110
                                                        Files.copy(src, Paths.get(this.reportDirPath + "/" + filename),
1✔
111
                                                                        StandardCopyOption.REPLACE_EXISTING);
112
                                                } catch (IOException ex) {
×
NEW
113
                                                        errorWith(COVERAGE_LOG)
×
NEW
114
                                                                        .setMessage(ex.getMessage())
×
NEW
115
                                                                        .setCause(ex)
×
NEW
116
                                                                        .log();
×
117
                                                }
1✔
118
                                        });
1✔
119
                        // write report
120
                        try (var writer = Files.newBufferedWriter(this.reportDirPath.resolve("index.html"),
1✔
121
                                        StandardCharsets.UTF_8)) {
122
                                writePrefix(writer);
1✔
123

124
                                writeHeaderSection(writer);
1✔
125

126
                                writeTablePrefix(writer);
1✔
127
                                writeTable(writer);
1✔
128
                                writeTableSuffix(writer);
1✔
129

130
                                writeSuffix(writer);
1✔
131
                        }
132
                } catch (IOException ex) {
×
NEW
133
                        errorWith(COVERAGE_LOG)
×
NEW
134
                                        .setMessage(ex.getMessage())
×
NEW
135
                                        .setCause(ex)
×
NEW
136
                                        .log();
×
137
                }
1✔
138
        }
1✔
139

140
        private void writeTable(final BufferedWriter writer) throws IOException {
141
                var list = coverages.values().stream()
1✔
142
                                .map(Map::values)
1✔
143
                                .flatMap(Collection::stream)
1✔
144
                                .sorted(Comparator.comparing(SqlCoverageReport::getName))
1✔
145
                                .collect(Collectors.toList());
1✔
146
                for (var sqlCoverageReport : list) {
1✔
147
                        var htmlName = sqlCoverageReport.getName();
1✔
148
                        var linkName = sqlCoverageReport.getName();
1✔
149
                        var lineCount = sqlCoverageReport.getLineValidSize();
1✔
150
                        var lineCovered = sqlCoverageReport.getLineCoveredSize();
1✔
151

152
                        var branchesCount = sqlCoverageReport.getBranchValidSize();
1✔
153
                        var branchesCovered = sqlCoverageReport.getBranchCoveredSize();
1✔
154

155
                        var lineCoveredPer = CoverageHandler.percent(lineCovered, lineCount);
1✔
156

157
                        writer.append("<tr>");
1✔
158
                        writer.newLine();
1✔
159
                        writer.append("   <td class=\"file\" ><a href=\"").append(linkName).append(".html\" >").append(htmlName)
1✔
160
                                        .append("</a></td>");
1✔
161
                        writer.newLine();
1✔
162
                        writer.append(
1✔
163
                                        "   <td class=\"pic\" ><div class=\"chart\"><div class=\"cover-fill\" style=\"width: ")
164
                                        .append(String.valueOf(lineCoveredPer))
1✔
165
                                        .append("%;\"></div><div class=\"cover-empty\" style=\"width:")
1✔
166
                                        .append(String.valueOf(100 - lineCoveredPer)).append("%;\"></div></div></td>");
1✔
167
                        writer.newLine();
1✔
168
                        writer.append("   <td class=\"lines\">").append(String.valueOf(lineCoveredPer))
1✔
169
                                        .append("%</td>");
1✔
170
                        writer.newLine();
1✔
171
                        writer.append("   <td class=\"lines-raw\">").append(String.valueOf(lineCovered)).append("/")
1✔
172
                                        .append(String.valueOf(lineCount)).append("</td>");
1✔
173
                        writer.newLine();
1✔
174
                        writer.append("   <td class=\"branches\">")
1✔
175
                                        .append(CoverageHandler.percentStr(branchesCovered, branchesCount))
1✔
176
                                        .append("%</td>");
1✔
177
                        writer.newLine();
1✔
178
                        writer.append("   <td class=\"branches-raw\">").append(String.valueOf(branchesCovered)).append("/")
1✔
179
                                        .append(String.valueOf(branchesCount)).append("</td>");
1✔
180
                        writer.newLine();
1✔
181
                        writer.append("</tr>");
1✔
182
                        writer.newLine();
1✔
183
                }
1✔
184
        }
1✔
185

186
        private void writePrefix(final BufferedWriter writer) throws IOException {
187
                writer.append("<!DOCTYPE html>");
1✔
188
                writer.newLine();
1✔
189
                writer.append("<html lang=\"en\">");
1✔
190
                writer.newLine();
1✔
191
                writer.append("<head>");
1✔
192
                writer.newLine();
1✔
193
                writer.append("    <meta charset=\"utf-8\" />");
1✔
194
                writer.newLine();
1✔
195
                writer.append("    <title>uroboroSQL code coverage report</title>");
1✔
196
                writer.newLine();
1✔
197
                writer.append("    <link rel=\"stylesheet\" href=\"style.css\">");
1✔
198
                writer.newLine();
1✔
199
                writer.append("    <script src=\"jquery-3.2.0.min.js\"></script>");
1✔
200
                writer.newLine();
1✔
201
                writer.append("    <script src=\"stupidtable.min.js\"></script>");
1✔
202
                writer.newLine();
1✔
203
                writer.append("    <script>$(function(){ $(\"table.coverage-summary\").stupidtable(); });</script>");
1✔
204
                writer.newLine();
1✔
205
                writer.append("</head>");
1✔
206
                writer.newLine();
1✔
207
                writer.append("<body>");
1✔
208
                writer.newLine();
1✔
209
        }
1✔
210

211
        private void writeHeaderSection(final BufferedWriter writer) throws IOException {
212
                var lineCount = coverages.values().stream()
1✔
213
                                .map(Map::values)
1✔
214
                                .flatMap(Collection::stream)
1✔
215
                                .mapToInt(SqlCoverageReport::getLineValidSize)
1✔
216
                                .sum();
1✔
217
                var lineCovered = coverages.values().stream()
1✔
218
                                .map(Map::values)
1✔
219
                                .flatMap(Collection::stream)
1✔
220
                                .mapToInt(SqlCoverageReport::getLineCoveredSize)
1✔
221
                                .sum();
1✔
222

223
                var branchesCount = coverages.values().stream()
1✔
224
                                .map(Map::values)
1✔
225
                                .flatMap(Collection::stream)
1✔
226
                                .mapToInt(SqlCoverageReport::getBranchValidSize)
1✔
227
                                .sum();
1✔
228
                var branchesCovered = coverages.values().stream()
1✔
229
                                .map(Map::values)
1✔
230
                                .flatMap(Collection::stream)
1✔
231
                                .mapToInt(SqlCoverageReport::getBranchCoveredSize)
1✔
232
                                .sum();
1✔
233

234
                writer.append("<div class=\"global-header\">");
1✔
235
                writer.newLine();
1✔
236
                writer.append("    <img class=\"icon\" src=\"icon.png\" />");
1✔
237
                writer.newLine();
1✔
238
                writer.append("    <span class=\"title\">uroboroSQL coverage</span>");
1✔
239
                writer.newLine();
1✔
240
                writer.append("</div>");
1✔
241
                writer.newLine();
1✔
242
                writer.append("<h1>Code coverage report Summary</h1>");
1✔
243
                writer.newLine();
1✔
244
                writer.append("<div class=\"header\">");
1✔
245
                writer.newLine();
1✔
246
                writer.append("    <div class=\"summary\">");
1✔
247
                writer.newLine();
1✔
248
                writer.append("      <strong>").append(CoverageHandler.percentStr(lineCovered, lineCount))
1✔
249
                                .append("% </strong>");
1✔
250
                writer.newLine();
1✔
251
                writer.append("      <span>Lines</span>");
1✔
252
                writer.newLine();
1✔
253
                writer.append("      <span class=\"fraction\">").append(String.valueOf(lineCovered)).append("/")
1✔
254
                                .append(String.valueOf(lineCount)).append("</span>");
1✔
255
                writer.newLine();
1✔
256
                writer.append("    </div>");
1✔
257
                writer.newLine();
1✔
258
                writer.append("    <div class=\"summary\">");
1✔
259
                writer.newLine();
1✔
260
                writer.append("      <strong>").append(CoverageHandler.percentStr(branchesCovered, branchesCount))
1✔
261
                                .append("% </strong>");
1✔
262
                writer.newLine();
1✔
263
                writer.append("      <span>Branches</span>");
1✔
264
                writer.newLine();
1✔
265
                writer.append("      <span class=\"fraction\">").append(String.valueOf(branchesCovered)).append("/")
1✔
266
                                .append(String.valueOf(branchesCount)).append("</span>");
1✔
267
                writer.newLine();
1✔
268
                writer.append("    </div>");
1✔
269
                writer.newLine();
1✔
270
                writer.append("</div>");
1✔
271
                writer.newLine();
1✔
272
        }
1✔
273

274
        private void writeTablePrefix(final BufferedWriter writer) throws IOException {
275
                writer.append("<div class=\"inner\">");
1✔
276
                writer.newLine();
1✔
277
                writer.append("<table class=\"coverage-summary\">");
1✔
278
                writer.newLine();
1✔
279
                writer.append("<thead>");
1✔
280
                writer.newLine();
1✔
281
                writer.append("<tr>");
1✔
282
                writer.newLine();
1✔
283
                writer.append("   <th data-sort=\"string\" class=\"file sorting-asc\" >File</th>");
1✔
284
                writer.newLine();
1✔
285
                writer.append("   <th class=\"pic\" ></th>");
1✔
286
                writer.newLine();
1✔
287
                writer.append("   <th data-sort=\"int\" class=\"lines\" >Lines</th>");
1✔
288
                writer.newLine();
1✔
289
                writer.append("   <th class=\"lines-raw\"></th>");
1✔
290
                writer.newLine();
1✔
291
                writer.append("   <th data-sort=\"int\" class=\"branches\" >Branches</th>");
1✔
292
                writer.newLine();
1✔
293
                writer.append("   <th class=\"branches-raw\"></th>");
1✔
294
                writer.newLine();
1✔
295
                writer.append("</tr>");
1✔
296
                writer.newLine();
1✔
297
                writer.append("</thead>");
1✔
298
                writer.newLine();
1✔
299
                writer.append("<tbody>");
1✔
300
                writer.newLine();
1✔
301

302
        }
1✔
303

304
        private void writeTableSuffix(final BufferedWriter writer) throws IOException {
305
                writer.append("</tbody>");
1✔
306
                writer.newLine();
1✔
307
                writer.append("</table>");
1✔
308
                writer.newLine();
1✔
309
                writer.append("</div>");
1✔
310
                writer.newLine();
1✔
311
                writer.append("<div class=\"footer\">code coverage report generated by ")
1✔
312
                                .append("<a href=\"https://github.com/future-architect/uroborosql\" target=\"_blank\">uroboroSQL</a> at ")
1✔
313
                                .append(ZonedDateTime.now().format(DateTimeFormatter.ISO_ZONED_DATE_TIME))
1✔
314
                                .append(".</div>");
1✔
315
                writer.newLine();
1✔
316
        }
1✔
317

318
        private void writeSuffix(final BufferedWriter writer) throws IOException {
319
                writer.append("</body>");
1✔
320
        }
1✔
321
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc