• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Camelcade / Perl5-IDEA / #525521576

05 Jun 2025 06:17AM UTC coverage: 82.298% (-0.02%) from 82.318%
#525521576

push

github

hurricup
Localized strings and improved annotations

26 of 41 new or added lines in 19 files covered. (63.41%)

22 existing lines in 6 files now uncovered.

30837 of 37470 relevant lines covered (82.3%)

0.82 hits per line

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

53.57
/plugin/coverage/src/main/java/com/perl5/lang/perl/coverage/PerlCoverageRunner.java
1
/*
2
 * Copyright 2015-2025 Alexandr Evstigneev
3
 *
4
 * Licensed under the Apache License, Version 2.0 (the "License");
5
 * you may not use this file except in compliance with the License.
6
 * You may obtain a copy of the License at
7
 *
8
 * http://www.apache.org/licenses/LICENSE-2.0
9
 *
10
 * Unless required by applicable law or agreed to in writing, software
11
 * distributed under the License is distributed on an "AS IS" BASIS,
12
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
 * See the License for the specific language governing permissions and
14
 * limitations under the License.
15
 */
16

17
package com.perl5.lang.perl.coverage;
18

19
import com.google.gson.Gson;
20
import com.google.gson.JsonParseException;
21
import com.intellij.coverage.CoverageEngine;
22
import com.intellij.coverage.CoverageRunner;
23
import com.intellij.coverage.CoverageSuite;
24
import com.intellij.execution.ExecutionException;
25
import com.intellij.execution.process.ProcessOutput;
26
import com.intellij.notification.Notification;
27
import com.intellij.notification.NotificationType;
28
import com.intellij.notification.Notifications;
29
import com.intellij.openapi.application.ApplicationManager;
30
import com.intellij.openapi.application.ReadAction;
31
import com.intellij.openapi.diagnostic.Logger;
32
import com.intellij.openapi.progress.ProgressManager;
33
import com.intellij.openapi.project.Project;
34
import com.intellij.openapi.projectRoots.Sdk;
35
import com.intellij.openapi.util.NlsSafe;
36
import com.intellij.openapi.util.Ref;
37
import com.intellij.openapi.util.io.FileUtil;
38
import com.intellij.openapi.util.text.StringUtil;
39
import com.intellij.openapi.vfs.VirtualFile;
40
import com.intellij.rt.coverage.data.ClassData;
41
import com.intellij.rt.coverage.data.LineCoverage;
42
import com.intellij.rt.coverage.data.LineData;
43
import com.intellij.rt.coverage.data.ProjectData;
44
import com.perl5.PerlBundle;
45
import com.perl5.lang.perl.idea.execution.PerlCommandLine;
46
import com.perl5.lang.perl.idea.sdk.host.PerlHostData;
47
import com.perl5.lang.perl.util.PerlPluginUtil;
48
import com.perl5.lang.perl.util.PerlRunUtil;
49
import org.jetbrains.annotations.NotNull;
50
import org.jetbrains.annotations.Nullable;
51

52
import java.io.File;
53
import java.util.Collections;
54
import java.util.Map;
55
import java.util.Set;
56

57
public class PerlCoverageRunner extends CoverageRunner {
1✔
58
  private static final String COVER = "cover";
59
  private static final String COVER_LIB = "Devel::Cover";
60
  private static final Logger LOG = Logger.getInstance(PerlCoverageRunner.class);
1✔
61

62
  @Override
63
  public ProjectData loadCoverageData(@NotNull File sessionDataFile, @Nullable CoverageSuite baseCoverageSuite) {
64
    if (!(baseCoverageSuite instanceof PerlCoverageSuite perlCoverageSuite)) {
1✔
65
      return null;
×
66
    }
67
    if (ApplicationManager.getApplication().isDispatchThread()) {
1✔
68
      final Ref<ProjectData> projectDataRef = new Ref<>();
×
69

70
      ProgressManager.getInstance().runProcessWithProgressSynchronously(
×
71
        () -> projectDataRef.set(doLoadCoverageData(sessionDataFile, (PerlCoverageSuite)baseCoverageSuite)),
×
NEW
72
        PerlCoverageBundle.message("dialog.title.loading.coverage.data"), true, baseCoverageSuite.getProject());
×
73

74
      return projectDataRef.get();
×
75
    }
76
    else {
77
      return doLoadCoverageData(sessionDataFile, perlCoverageSuite);
1✔
78
    }
79
  }
80

81
  private static @Nullable ProjectData doLoadCoverageData(@NotNull File sessionDataFile, @NotNull PerlCoverageSuite perlCoverageSuite) {
82
    Project project = perlCoverageSuite.getProject();
1✔
83
    Sdk effectiveSdk;
84
    try {
85
      effectiveSdk = perlCoverageSuite.getConfiguration().getEffectiveSdk();
1✔
86
    }
87
    catch (ExecutionException e) {
×
88
      LOG.error(e);
×
89
      return null;
×
90
    }
1✔
91

92
    PerlHostData<?, ?> hostData = PerlHostData.from(effectiveSdk);
1✔
93
    if (hostData == null) {
1✔
94
      LOG.warn("No host data for " + effectiveSdk);
×
95
      return null;
×
96
    }
97
    try {
98
      hostData.fixPermissionsRecursively(sessionDataFile.getAbsolutePath(), project);
1✔
99
    }
100
    catch (ExecutionException e) {
×
101
      LOG.warn("Error fixing permissions for " + sessionDataFile);
×
102
    }
1✔
103

104
    PerlCommandLine perlCommandLine = ReadAction.compute(() -> {
1✔
105
      if (project.isDisposed()) {
1✔
106
        LOG.debug("Project disposed");
×
107
        return null;
×
108
      }
109

110
      VirtualFile coverFile = PerlRunUtil.findLibraryScriptWithNotification(effectiveSdk, project, COVER, COVER_LIB);
1✔
111
      if (coverFile == null) {
1✔
112
        LOG.warn("No `cover` script found in " + effectiveSdk);
×
113
        return null;
×
114
      }
115

116
      PerlCommandLine commandLine = PerlRunUtil.getPerlCommandLine(
1✔
117
        project, effectiveSdk, coverFile,
118
        Collections.singletonList(PerlRunUtil.PERL_I + hostData.getRemotePath(PerlPluginUtil.getHelpersLibPath())),
1✔
119
        Collections.emptyList());
1✔
120

121
      if (commandLine == null) {
1✔
122
        LOG.warn("Unable to create a command line: " +
×
123
                 " project: " + project +
124
                 "; sdk:" + effectiveSdk +
125
                 "; coverageFile: " + coverFile);
126
        return null;
×
127
      }
128

129
      String remotePath = hostData.getRemotePath(sessionDataFile.getAbsolutePath());
1✔
130
      if (StringUtil.isEmpty(remotePath)) {
1✔
131
        LOG.warn("Unable to map remote path for: " + sessionDataFile.getAbsolutePath() + " in " + hostData);
×
132
        return null;
×
133
      }
134

135
      commandLine
1✔
136
        .addParameters("--silent", "--nosummary", "-report", "camelcade", remotePath);
1✔
137
      commandLine.withSdk(effectiveSdk);
1✔
138
      commandLine.withProject(project);
1✔
139

140
      return commandLine;
1✔
141
    });
142

143
    if (perlCommandLine == null) {
1✔
144
      return null;
×
145
    }
146

147
    try {
148
      LOG.info("Loading coverage by: " + perlCommandLine.getCommandLineString());
1✔
149
      ProcessOutput output = PerlHostData.execAndGetOutput(perlCommandLine);
1✔
150
      if (output.getExitCode() != 0) {
1✔
151
        String errorMessage = output.getStderr();
×
152
        if (StringUtil.isEmpty(errorMessage)) {
×
153
          errorMessage = output.getStdout();
×
154
        }
155

156
        if (!StringUtil.isEmpty(errorMessage)) {
×
157
          showError(project, errorMessage);
×
158
        }
159
        return null;
×
160
      }
161
      String stdout = output.getStdout();
1✔
162
      if (StringUtil.isEmpty(stdout)) {
1✔
163
        return null;
×
164
      }
165

166
      try {
167
        PerlFileCoverageData[] filesData = new Gson().fromJson(stdout, PerlFileCoverageData[].class);
1✔
168
        if (filesData != null) {
1✔
169
          return parsePerlFileData(PerlHostData.notNullFrom(effectiveSdk), filesData);
1✔
170
        }
171
      }
172
      catch (JsonParseException e) {
×
173
        LOG.warn("Error parsing JSON", e);
×
174
        showError(project, e.getMessage());
×
175
      }
×
176
    }
177
    catch (ExecutionException e) {
×
178
      LOG.warn("Error loading coverage", e);
×
179
      showError(project, e.getMessage());
×
180
    }
×
181
    return null;
×
182
  }
183

184
  private static @NotNull ProjectData parsePerlFileData(@NotNull PerlHostData<?, ?> hostData, @NotNull PerlFileCoverageData[] filesData) {
185
    ProjectData projectData = new ProjectData();
1✔
186
    for (PerlFileCoverageData perlFileCoverageData : filesData) {
1✔
187
      if (StringUtil.isEmpty(perlFileCoverageData.name) || perlFileCoverageData.lines == null) {
1✔
188
        LOG.warn("Name or lines is null in " + perlFileCoverageData);
×
189
        continue;
×
190
      }
191
      String localPath = hostData.getLocalPath(perlFileCoverageData.name);
1✔
192
      if (localPath == null) {
1✔
193
        continue;
×
194
      }
195
      ClassData classData = projectData.getOrCreateClassData(FileUtil.toSystemIndependentName(localPath));
1✔
196
      Set<Map.Entry<Integer, PerlLineData>> linesEntries = perlFileCoverageData.lines.entrySet();
1✔
197
      Integer maxLineNumber = linesEntries.stream().map(Map.Entry::getKey).max(Integer::compare).orElse(0);
1✔
198
      LineData[] linesData = new LineData[maxLineNumber + 1];
1✔
199
      for (Map.Entry<Integer, PerlLineData> lineEntry : linesEntries) {
1✔
200
        PerlLineData perlLineData = lineEntry.getValue();
1✔
201
        final Integer lineNumber = lineEntry.getKey();
1✔
202
        LineData lineData = new LineData(lineNumber, null) {
1✔
203
          @Override
204
          public int getStatus() {
205
            if (perlLineData.cover == 0) {
1✔
206
              return LineCoverage.NONE;
1✔
207
            }
208
            else if (perlLineData.cover < perlLineData.data) {
1✔
209
              return LineCoverage.PARTIAL;
×
210
            }
211
            return LineCoverage.FULL;
1✔
212
          }
213
        };
214
        lineData.setHits(perlLineData.cover);
1✔
215
        linesData[lineNumber] = lineData;
1✔
216
      }
1✔
217

218
      classData.setLines(linesData);
1✔
219
    }
220

221
    return projectData;
1✔
222
  }
223

224
  private static void showError(@NotNull Project project, @NlsSafe @NotNull String message) {
225
    ReadAction.run(() -> {
×
226
      if (!project.isDisposed()) {
×
227
        LOG.warn("Error loading coverage: " + message);
×
228
        Notifications.Bus.notify(
×
229
          new Notification(
230
            PerlBundle.message("perl.coverage.loading.error"),
×
231
            PerlBundle.message("perl.coverage.loading.error"),
×
232
            message,
233
            NotificationType.ERROR
234
          ),
235
          project
236
        );
237
      }
238
    });
×
239
  }
×
240

241
  @Override
242
  public @NotNull String getPresentableName() {
243
    return PerlBundle.message("perl.perl5");
×
244
  }
245

246
  @Override
247
  public @NotNull String getId() {
248
    return "Perl5CoverageRunner";
1✔
249
  }
250

251
  @Override
252
  public @NotNull String getDataFileExtension() {
253
    return "json";
1✔
254
  }
255

256
  @Override
257
  public boolean acceptsCoverageEngine(@NotNull CoverageEngine engine) {
258
    return engine instanceof PerlCoverageEngine;
×
259
  }
260
}
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

© 2026 Coveralls, Inc