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

Camelcade / Perl5-IDEA / #525521703

09 Nov 2025 10:57AM UTC coverage: 75.97% (+0.06%) from 75.91%
#525521703

push

github

hurricup
Safer work with variable name

14772 of 22647 branches covered (65.23%)

Branch coverage included in aggregate %.

1 of 1 new or added line in 1 file covered. (100.0%)

89 existing lines in 10 files now uncovered.

31098 of 37732 relevant lines covered (82.42%)

0.82 hits per line

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

52.17
/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.*;
22
import com.intellij.execution.ExecutionException;
23
import com.intellij.execution.process.ProcessOutput;
24
import com.intellij.notification.Notification;
25
import com.intellij.notification.NotificationType;
26
import com.intellij.notification.Notifications;
27
import com.intellij.openapi.application.ApplicationManager;
28
import com.intellij.openapi.application.ReadAction;
29
import com.intellij.openapi.diagnostic.Logger;
30
import com.intellij.openapi.progress.ProgressManager;
31
import com.intellij.openapi.project.Project;
32
import com.intellij.openapi.projectRoots.Sdk;
33
import com.intellij.openapi.util.NlsSafe;
34
import com.intellij.openapi.util.Ref;
35
import com.intellij.openapi.util.io.FileUtil;
36
import com.intellij.openapi.util.text.StringUtil;
37
import com.intellij.openapi.vfs.VirtualFile;
38
import com.intellij.rt.coverage.data.ClassData;
39
import com.intellij.rt.coverage.data.LineCoverage;
40
import com.intellij.rt.coverage.data.LineData;
41
import com.intellij.rt.coverage.data.ProjectData;
42
import com.perl5.PerlBundle;
43
import com.perl5.lang.perl.idea.execution.PerlCommandLine;
44
import com.perl5.lang.perl.idea.sdk.host.PerlHostData;
45
import com.perl5.lang.perl.util.PerlPluginUtil;
46
import com.perl5.lang.perl.util.PerlRunUtil;
47
import org.jetbrains.annotations.NotNull;
48
import org.jetbrains.annotations.Nullable;
49

50
import java.io.File;
51
import java.util.Collections;
52
import java.util.Map;
53
import java.util.Objects;
54
import java.util.Set;
55

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

61
  @Override
62
  protected @NotNull CoverageLoadingResult loadCoverageData(@NotNull File sessionDataFile,
63
                                                            @Nullable CoverageSuite baseCoverageSuite,
64
                                                            @NotNull CoverageLoadErrorReporter reporter) {
65
    if (!(baseCoverageSuite instanceof PerlCoverageSuite perlCoverageSuite)) {
1!
UNCOV
66
      return new FailedCoverageLoadingResult(
×
67
        "Wrong type of coverage suite: " +
UNCOV
68
        (baseCoverageSuite == null ? "null" : baseCoverageSuite.getClass().getCanonicalName()));
×
69
    }
70
    final Ref<ProjectData> projectDataRef = new Ref<>();
1✔
71
    if (ApplicationManager.getApplication().isDispatchThread()) {
1!
72

UNCOV
73
      ProgressManager.getInstance().runProcessWithProgressSynchronously(
×
UNCOV
74
        () -> projectDataRef.set(doLoadCoverageData(sessionDataFile, (PerlCoverageSuite)baseCoverageSuite)),
×
UNCOV
75
        PerlCoverageBundle.message("dialog.title.loading.coverage.data"), true, baseCoverageSuite.getProject());
×
76

77
    }
78
    else {
79
      projectDataRef.set(doLoadCoverageData(sessionDataFile, perlCoverageSuite));
1✔
80
    }
81
    return new SuccessCoverageLoadingResult(Objects.requireNonNull(projectDataRef.get()));
1✔
82
  }
83

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

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

107
    PerlCommandLine perlCommandLine = ReadAction.compute(() -> {
1✔
108
      if (project.isDisposed()) {
1!
UNCOV
109
        LOG.debug("Project disposed");
×
UNCOV
110
        return null;
×
111
      }
112

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

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

124
      if (commandLine == null) {
1!
UNCOV
125
        LOG.warn("Unable to create a command line: " +
×
126
                 " project: " + project +
127
                 "; sdk:" + effectiveSdk +
128
                 "; coverageFile: " + coverFile);
UNCOV
129
        return null;
×
130
      }
131

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

138
      commandLine
1✔
139
        .addParameters("--silent", "--nosummary", "-report", "camelcade", remotePath);
1✔
140
      commandLine.withSdk(effectiveSdk);
1✔
141
      commandLine.withProject(project);
1✔
142

143
      return commandLine;
1✔
144
    });
145

146
    if (perlCommandLine == null) {
1!
UNCOV
147
      return null;
×
148
    }
149

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

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

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

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

221
      classData.setLines(linesData);
1✔
222
    }
223

224
    return projectData;
1!
225
  }
226

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

244
  @Override
245
  public @NotNull String getPresentableName() {
UNCOV
246
    return PerlBundle.message("perl.perl5");
×
247
  }
248

249
  @Override
250
  public @NotNull String getId() {
251
    return "Perl5CoverageRunner";
1✔
252
  }
253

254
  @Override
255
  public @NotNull String getDataFileExtension() {
256
    return "json";
1✔
257
  }
258

259
  @Override
260
  public boolean acceptsCoverageEngine(@NotNull CoverageEngine engine) {
UNCOV
261
    return engine instanceof PerlCoverageEngine;
×
262
  }
263
}
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