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

MeindertN / RoboClerk / 19481929392

18 Nov 2025 10:05PM UTC coverage: 72.65% (+0.3%) from 72.366%
19481929392

push

github

MeindertN
Fixed failing test

2217 of 3233 branches covered (68.57%)

Branch coverage included in aggregate %.

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

526 existing lines in 15 files now uncovered.

6894 of 9308 relevant lines covered (74.07%)

96.09 hits per line

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

68.85
/RoboClerk.Core/PluginSupport/SourceCodeAnalysisPluginBase.cs
1
using RoboClerk.Core.Configuration;
2
using System;
3
using System.Collections.Generic;
4
using System.IO;
5
using System.IO.Abstractions;
6
using System.Linq;
7
using Tomlyn.Model;
8

9
namespace RoboClerk
10
{
11
    /// <summary>
12
    /// Represents a single test configuration with its own settings and associated files
13
    /// </summary>
14
    public class TestConfiguration
15
    {
16
        // Known/common fields with typed accessors
17
        public string Language { get; set; } = "csharp";
345✔
18
        public string TestDirectory { get; set; } = string.Empty;
333✔
19
        public bool SubDirs { get; set; } = true;
232✔
20
        public List<string> FileMasks { get; set; } = new List<string>();
215✔
21
        public string Project { get; set; } = string.Empty;
320✔
22
        
23
        // Dictionary for all fields (including unknown ones)
24
        public Dictionary<string, object> AllFields { get; private set; } = new Dictionary<string, object>();
817✔
25
        
26
        // Files associated with this configuration
27
        public List<string> SourceFiles { get; private set; } = new List<string>();
368✔
28

29
        public void FromToml(TomlTable toml)
30
        {
52✔
31
            // Store ALL fields from TOML first
32
            AllFields.Clear();
52✔
33
            foreach (var kvp in toml)
816✔
34
            {
330✔
35
                AllFields[kvp.Key] = kvp.Value;
330✔
36
            }
330✔
37
            
38
            // Then populate known typed properties
39
            if (AllFields.TryGetValue("Language", out var lang))
52✔
40
                Language = lang.ToString();
52✔
41
                
42
            if (AllFields.TryGetValue("TestDirectory", out var dir))
52✔
43
                TestDirectory = dir.ToString();
52✔
44
                
45
            if (AllFields.TryGetValue("SubDirs", out var sub))
52✔
46
                SubDirs = (bool)sub;
52✔
47
                
48
            if (AllFields.TryGetValue("FileMasks", out var masks) && masks is TomlArray masksArray)
52!
49
            {
52✔
50
                FileMasks.Clear();
52✔
51
                foreach (var obj in masksArray)
278✔
52
                {
61✔
53
                    FileMasks.Add((string)obj);
61✔
54
                }
61✔
55
            }
52✔
56
            
57
            if (AllFields.TryGetValue("Project", out var proj))
52✔
58
                Project = proj.ToString();
52✔
59
        }
52✔
60
        
61
        /// <summary>
62
        /// Get a typed value from the configuration, with optional default
63
        /// </summary>
64
        public T GetValue<T>(string key, T defaultValue = default(T))
65
        {
123✔
66
            if (AllFields.TryGetValue(key, out var value))
123!
67
            {
123✔
68
                if (value is T typedValue)
123!
69
                    return typedValue;
123✔
70
                
71
                // Try to convert common types
UNCOV
72
                if (typeof(T) == typeof(string))
×
UNCOV
73
                    return (T)(object)value.ToString();
×
74
                
UNCOV
75
                if (typeof(T) == typeof(bool) && bool.TryParse(value.ToString(), out var boolVal))
×
UNCOV
76
                    return (T)(object)boolVal;
×
77
                    
78
                // Add other type conversions as needed
UNCOV
79
            }
×
80
            
UNCOV
81
            return defaultValue;
×
82
        }
123✔
83
        
84
        /// <summary>
85
        /// Check if a field exists in the configuration
86
        /// </summary>
UNCOV
87
        public bool HasField(string key) => AllFields.ContainsKey(key);
×
88
        
89
        /// <summary>
90
        /// Add a source file to this configuration
91
        /// </summary>
92
        internal void AddSourceFile(string filePath)
93
        {
55✔
94
            SourceFiles.Add(filePath);
55✔
95
        }
55✔
96
        
97
        /// <summary>
98
        /// Clear all source files for this configuration
99
        /// </summary>
100
        internal void ClearSourceFiles()
101
        {
49✔
102
            SourceFiles.Clear();
49✔
103
        }
49✔
104
    }
105

106
    public abstract class SourceCodeAnalysisPluginBase : DataSourcePluginBase
107
    {
108
        // Configuration-based approach
109
        protected List<TestConfiguration> testConfigurations = new List<TestConfiguration>();
51✔
110
        protected List<string> sourceFiles = new List<string>();
51✔
111
        protected GitRepository gitRepo = null;
51✔
112

113
        public SourceCodeAnalysisPluginBase(IFileProviderPlugin fileSystem)
114
            : base(fileSystem)
51✔
115
        {
51✔
116
        }
51✔
117

118
        public override void InitializePlugin(IConfiguration configuration)
119
        {
50✔
120
            var config = GetConfigurationTable(configuration.PluginConfigDir, $"{name}.toml");
50✔
121
            
122
            // Check if using TestConfigurations format
123
            if (config.ContainsKey("TestConfigurations"))
48✔
124
            {
47✔
125
                InitializeTestConfigurations(config);
47✔
126
            }
47✔
127
            else
128
            {
1✔
129
                throw new Exception($"TestConfigurations section is required in {name}.toml configuration file.");
1✔
130
            }
131

132
            try
133
            {
47✔
134
                if (config.ContainsKey("UseGit") && (bool)config["UseGit"])
47!
UNCOV
135
                {
×
UNCOV
136
                    gitRepo = new GitRepository(configuration, fileProvider);
×
UNCOV
137
                }
×
138
            }
47✔
UNCOV
139
            catch (Exception)
×
UNCOV
140
            {
×
UNCOV
141
                logger.Error($"Error opening git repo at project root \"{configuration.ProjectRoot}\" even though the {name}.toml configuration file UseGit setting was set to true.");
×
UNCOV
142
                throw;
×
143
            }
144
        }
47✔
145

146
        private void InitializeTestConfigurations(TomlTable config)
147
        {
47✔
148
            testConfigurations.Clear();
47✔
149
            var configurationsArray = (TomlTableArray)config["TestConfigurations"];
47✔
150
            
151
            foreach (TomlTable configTable in configurationsArray)
245✔
152
            {
52✔
153
                var testConfig = new TestConfiguration();
52✔
154
                testConfig.FromToml(configTable);
52✔
155
                testConfigurations.Add(testConfig);
52✔
156
            }
52✔
157

158
            if (testConfigurations.Count == 0)
47!
UNCOV
159
            {
×
UNCOV
160
                throw new Exception($"No test configurations found in {name}.toml. At least one TestConfiguration is required.");
×
161
            }
162
        }
47✔
163

164
        protected void ScanDirectoriesForSourceFiles()
165
        {
45✔
166
            // Clear all files from configurations first
167
            foreach (var testConfig in testConfigurations)
233✔
168
            {
49✔
169
                testConfig.ClearSourceFiles();
49✔
170
            }
49✔
171
            
172
            // Clear the legacy sourceFiles list (keep for backward compatibility)
173
            sourceFiles.Clear();
45✔
174
            
175
            foreach (var testConfig in testConfigurations)
232✔
176
            {
49✔
177
                ScanDirectoryForConfiguration(testConfig);
49✔
178
            }
48✔
179

180
            // Count total files across all configurations
181
            var totalFiles = testConfigurations.Sum(config => config.SourceFiles.Count);
91✔
182
            logger.Info($"Found {totalFiles} source files across {testConfigurations.Count} configurations");
44✔
183
            
184
            // Preload git information for performance if git is enabled and we have files
185
            if (gitRepo != null && totalFiles > 0)
44!
UNCOV
186
            {
×
UNCOV
187
                logger.Debug($"Preloading git information for {totalFiles} source files across {testConfigurations.Count} configurations");
×
188
                
189
                // Get unique directories that contain our source files
UNCOV
190
                var sourceDirectories = testConfigurations
×
UNCOV
191
                    .SelectMany(config => config.SourceFiles)
×
UNCOV
192
                    .Select(f => Path.GetDirectoryName(f))
×
UNCOV
193
                    .Where(d => !string.IsNullOrEmpty(d))
×
UNCOV
194
                    .Distinct()
×
UNCOV
195
                    .ToList();
×
196

197
                // Preload git information for each directory
UNCOV
198
                foreach (var directory in sourceDirectories)
×
UNCOV
199
                {
×
200
                    try
UNCOV
201
                    {
×
UNCOV
202
                        gitRepo.PreloadDirectoryInfo(directory);
×
UNCOV
203
                    }
×
UNCOV
204
                    catch (Exception ex)
×
UNCOV
205
                    {
×
UNCOV
206
                        logger.Warn($"Could not preload git information for directory {directory}: {ex.Message}");
×
207
                        // Continue processing other directories even if one fails
UNCOV
208
                    }
×
UNCOV
209
                }
×
UNCOV
210
            }
×
211
        }
44✔
212

213
        private void ScanDirectoryForConfiguration(TestConfiguration testConfig)
214
        {
49✔
215
            if (string.IsNullOrEmpty(testConfig.TestDirectory))
49!
UNCOV
216
            {
×
UNCOV
217
                logger.Warn($"Empty TestDirectory in configuration for project '{testConfig.Project}', skipping");
×
UNCOV
218
                return;
×
219
            }
220

221
            if (!fileProvider.DirectoryExists(testConfig.TestDirectory))
49✔
222
            {
1✔
223
                logger.Error($"Directory {testConfig.TestDirectory} for project '{testConfig.Project}' does not exist");
1✔
224
                throw new DirectoryNotFoundException($"Directory not found: {testConfig.TestDirectory}");
1✔
225
            }
226

227
            try
228
            {
48✔
229
                foreach (var fileMask in testConfig.FileMasks)
258✔
230
                {
57✔
231
                    string[] files = fileProvider.GetFiles(testConfig.TestDirectory, fileMask, testConfig.SubDirs ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly);
57✔
232
                    foreach (var file in files)
281✔
233
                    {
55✔
234
                        testConfig.AddSourceFile(file);
55✔
235
                        sourceFiles.Add(file); // Keep for backward compatibility
55✔
236
                        logger.Debug($"Found source file: {file} (Project: {testConfig.Project}, Language: {testConfig.Language})");
55✔
237
                    }
55✔
238
                }
57✔
239
                
240
                logger.Info($"Configuration '{testConfig.Project}' ({testConfig.Language}): Found {testConfig.SourceFiles.Count} source files");
48✔
241
            }
48✔
UNCOV
242
            catch (Exception ex)
×
UNCOV
243
            {
×
UNCOV
244
                logger.Error($"Error reading directory {testConfig.TestDirectory} for project '{testConfig.Project}': {ex.Message}");
×
UNCOV
245
                throw;
×
246
            }
247
        }
48✔
248

249
        /// <summary>
250
        /// Get the test configuration that applies to a specific source file path
251
        /// </summary>
252
        /// <param name="filePath">The path to the source file</param>
253
        /// <returns>The TestConfiguration that matches this file, or null if not found</returns>
254
        protected TestConfiguration GetConfigurationForFile(string filePath)
255
        {
70✔
256
            foreach (var config in testConfigurations)
280!
257
            {
70✔
258
                if (IsFileInDirectory(filePath, config.TestDirectory, config.SubDirs))
70!
259
                {
70✔
260
                    return config;
70✔
261
                }
UNCOV
262
            }
×
UNCOV
263
            return null;
×
264
        }
70✔
265

266
        private bool IsFileInDirectory(string filePath, string directoryPath, bool includeSubDirs)
267
        {
70✔
268
            try
269
            {
70✔
270
                string fileDir = fileProvider.GetDirectoryName(filePath);
70✔
271
                string fullDirectoryPath = fileProvider.GetFullPath(directoryPath);
70✔
272
                
273
                while (!string.IsNullOrEmpty(fileDir))
70!
274
                {
70✔
275
                    if (string.Equals(fileProvider.GetFullPath(fileDir), fullDirectoryPath, StringComparison.OrdinalIgnoreCase))
70!
276
                    {
70✔
277
                        return true;
70✔
278
                    }
279
                    
UNCOV
280
                    if (!includeSubDirs)
×
UNCOV
281
                    {
×
UNCOV
282
                        break;
×
283
                    }
284
                    
UNCOV
285
                    fileDir = fileProvider.GetDirectoryName(fileDir);
×
UNCOV
286
                }
×
287
                
UNCOV
288
                return false;
×
289
            }
UNCOV
290
            catch
×
UNCOV
291
            {
×
UNCOV
292
                return false;
×
293
            }
294
        }
70✔
295

296
        /// <summary>
297
        /// Gets all test configurations
298
        /// </summary>
299
        protected IReadOnlyList<TestConfiguration> TestConfigurations => testConfigurations.AsReadOnly();
118✔
300
    }
301
}
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