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

AnderssonPeter / PowerType / 5536490061

pending completion
5536490061

push

github

AnderssonPeter
Update DynamicSourceCache when executing specific commands

316 of 492 branches covered (64.23%)

Branch coverage included in aggregate %.

58 of 58 new or added lines in 6 files covered. (100.0%)

676 of 948 relevant lines covered (71.31%)

177.77 hits per line

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

69.81
/PowerType/BackgroundProcessing/ExecutionEngineThread.cs
1
using System.Management.Automation;
2
using System.Management.Automation.Runspaces;
3
using System.Reflection;
4
using System.Runtime.InteropServices;
5
using Microsoft.PowerShell;
6
using PowerType.Model;
7

8
namespace PowerType.BackgroundProcessing;
9

10
internal class ExecutionEngineThread : IDisposable
11
{
12
    private bool disposed;
13
    record DictionaryInformation(string File, DictionarySuggester Suggester);
57✔
14
    private readonly object dictionariesLocker = new();
15✔
15
    private readonly List<DictionaryInformation> dictionaries = new();
15✔
16
    private readonly ThreadQueue<Command> queue;
17
    private readonly CancellationTokenSource cancellationTokenSource = new();
15✔
18
    private readonly CancellationToken cancellationToken;
19
    private readonly Thread backgroundThread;
20
    private Exception? backgroundThreadException;
21
    private readonly Runspace runspace;
22

23
    internal ExecutionEngineThread(ThreadQueue<Command> queue)
15✔
24
    {
25
        this.queue = queue;
15✔
26
        this.cancellationToken = cancellationTokenSource.Token;
15✔
27
        backgroundThread = new Thread(InnerLoop);
15✔
28
        backgroundThread.IsBackground = true;
15✔
29
        backgroundThread.Start();
15✔
30
        runspace = CreateRunspace();
15✔
31
    }
15✔
32

33
    /// <summary>This method is thread safe</summary>
34
    public bool IsHealthy(out Exception? exception)
35
    {
36
        Thread.MemoryBarrier();
113✔
37
        exception = backgroundThreadException;
113✔
38
        return exception == null && backgroundThread.IsAlive;
113!
39
    }
40

41
    /// <summary>This method is thread safe</summary>
42
    public List<DictionarySuggester> GetSuggesters()
43
    {
44
        lock (dictionariesLocker)
3✔
45
        {
46
            return dictionaries.ConvertAll(x => x.Suggester);
6✔
47
        }
48
    }
3✔
49

50
    /// <summary>This method is thread safe</summary>
51
    public List<PowerTypeDictionary> GetDictionaries()
52
    {
53
        lock (dictionariesLocker)
6✔
54
        {
55
            return dictionaries.ConvertAll(x => x.Suggester.Dictionary);
12✔
56
        }
57
    }
6✔
58

59
    private void InnerLoop()
60
    {
61
        try
62
        {
63
            while (!cancellationToken.IsCancellationRequested)
33!
64
            {
65
                var command = queue.WaitAndDequeue(cancellationToken);
33✔
66
                if (command is InitializeDictionaryCommand initializeCommand)
18✔
67
                {
68
                    Handle(initializeCommand);
12✔
69
                }
70
                else if (command is CacheDictionaryDynamicSourcesCommand cacheDictionaryCommand)
6!
71
                {
72
                    Handle(cacheDictionaryCommand);
6✔
73
                }
74
                else if (command is CommandExecutedCommand commandExecutedCommand)
×
75
                {
76
                    Handle(commandExecutedCommand);
×
77
                }
78
                else
79
                {
80
                    throw new InvalidOperationException($"Don't know how to handle command {command.GetType()}!");
×
81
                }
82
                command.IsDone = true;
18✔
83
            }
84
        }
×
85
        catch (Exception ex)
15✔
86
        {
87
            backgroundThreadException = ex;
15✔
88
        }
15✔
89
    }
15✔
90

91
    private static Runspace CreateRunspace()
92
    {
93
        var assembly = Assembly.GetExecutingAssembly();
15✔
94
        var assemblyName = assembly.GetName().Name;
15✔
95
        var assemblyPath = assembly.Location;
15✔
96
        var assemblyDirectory = Path.GetDirectoryName(assembly.Location) ?? throw new InvalidOperationException("Failed to get assembly directory!");
15!
97
        var modulePath = assembly.Location.Replace(".dll", ".psd1");
15✔
98
        var initialState = InitialSessionState.CreateDefault();
15✔
99
        initialState.ThrowOnRunspaceOpenError = true;
15✔
100
        initialState.Assemblies.Add(new SessionStateAssemblyEntry(assemblyName, assemblyPath));
15✔
101
        initialState.ImportPSModule(modulePath);
15✔
102
        if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
15✔
103
        {
104
            initialState.ExecutionPolicy = ExecutionPolicy.Unrestricted;
5✔
105
        }
106
        var runspace = RunspaceFactory.CreateRunspace(initialState);
15✔
107
        runspace.Open();
15✔
108

109
        using var powershell = PowerShell.Create(runspace);
15✔
110
        powershell.AddScript($"using namespace PowerType.Model\nusing namespace PowerType.Model.Conditions\n. '{Path.Combine(assemblyDirectory, "HelperFunctions.ps1")}'");
15✔
111

112
        var result = powershell.Invoke();
15✔
113
        if (powershell.HadErrors)
15!
114
        {
115
            var errors = string.Join(Environment.NewLine, powershell.Streams.Error.Select(x => x.ToString()));
×
116
            throw new Exception("Failed to initialize dictionary, errors: " + errors);
×
117
        }
118
        return runspace;
15✔
119
    }
15✔
120

121
    private void Handle(InitializeDictionaryCommand command)
122
    {
123
        using var powershell = PowerShell.Create(runspace);
12✔
124
        powershell.AddScript($". '{command.File}'");
12✔
125
        var result = powershell.Invoke();
12✔
126
        if (powershell.HadErrors)
12!
127
        {
128
            var errors = string.Join(Environment.NewLine, powershell.Streams.Error.Select(x => x.ToString()));
×
129
            throw new Exception("Failed to initialize dictionary, errors: " + errors);
×
130
        }
131

132
        DictionarySuggester suggester;
133
        var resultObject = result.Single().BaseObject;
12✔
134
        if (resultObject is PowerTypeDictionary dictionary)
12!
135
        {
136
            if (!dictionary.Platforms.HasFlag(PlatformIdentification.CurrentPlatform))
12!
137
            {
138
                //This dictionary is not supported on the current platform
139
                return;
×
140
            }
141
            dictionary.Initialize(SystemTime.Instance);
12✔
142
            dictionary.Validate();
12✔
143
            suggester = new DictionarySuggester(dictionary);
12✔
144
        }
145
        else if (resultObject is DictionarySuggester suggesterTemp)
×
146
        {
147
            suggester = suggesterTemp;
×
148
        }
149
        else
150
        {
151
            throw new InvalidOperationException("Didn't receive a PowerTypeDictionary or ISuggester");
×
152
        }
153
        lock (dictionariesLocker)
12✔
154
        {
155
            dictionaries.Add(new DictionaryInformation(command.File, suggester));
12✔
156
        }
12✔
157
    }
12✔
158

159
    private void Handle(CacheDictionaryDynamicSourcesCommand command)
160
    {
161
        var dictionaryInformation = GetDictionaryInformation(command.Dictionary);
6✔
162

163
        foreach (var dynamicSource in dictionaryInformation.Suggester.GetDynamicSources())
24✔
164
        {
165
            if (dynamicSource.Cache.ShouldUpdate(command.CurrentWorkingDirectory))
6✔
166
            {
167
                try
168
                {
169
                    runspace.SessionStateProxy.Path.SetLocation(command.CurrentWorkingDirectory);
6✔
170
                    var result = dynamicSource.CommandExpression.Invoke();
3✔
171
                    var items = result.Select(x => x.BaseObject is string value ?
3!
172
                        new SourceItem { Name = value } :
3✔
173
                        (SourceItem)x.BaseObject).ToList();
3✔
174
                    dynamicSource.Cache.UpdateCache(items, command.CurrentWorkingDirectory);
3✔
175
                }
3✔
176
                catch(System.Management.Automation.DriveNotFoundException)
3✔
177
                {
178
                    //todo: log exception
179
                }
3✔
180
            }
181
        }
182
    }
6✔
183

184
    private void Handle(CommandExecutedCommand command)
185
    {
186
        var dictionaryInformation = GetDictionaryInformation(command.Dictionary);
×
187

188
        foreach (var dynamicSource in dictionaryInformation.Suggester.GetDynamicSources())
×
189
        {
190
            if (dynamicSource.Cache.ShouldUpdate(command.CurrentWorkingDirectory, command.Command))
×
191
            {
192
                try
193
                {
194
                    runspace.SessionStateProxy.Path.SetLocation(command.CurrentWorkingDirectory);
×
195
                    var result = dynamicSource.CommandExpression.Invoke();
×
196
                    var items = result.Select(x => x.BaseObject is string value ?
×
197
                        new SourceItem { Name = value } :
×
198
                        (SourceItem)x.BaseObject).ToList();
×
199
                    dynamicSource.Cache.UpdateCache(items, command.CurrentWorkingDirectory);
×
200
                }
×
201
                catch (System.Management.Automation.DriveNotFoundException)
×
202
                {
203
                    //todo: log exception
204
                }
×
205
            }
206
        }
207
    }
×
208

209
    private DictionaryInformation GetDictionaryInformation(PowerTypeDictionary dictionary)
210
    {
211
        lock (dictionariesLocker)
6✔
212
        {
213
            return this.dictionaries.Single(x => x.Suggester is DictionarySuggester suggestor && suggestor.Dictionary == dictionary);
12!
214
        }
215
    }
6✔
216

217
    public void Dispose()
218
    {
219
        // Dispose of unmanaged resources.
220
        Dispose(true);
15✔
221
        // Suppress finalization.
222
        GC.SuppressFinalize(this);
15✔
223
    }
15✔
224

225
    protected virtual void Dispose(bool disposing)
226
    {
227
        if (disposed)
15!
228
        {
229
            return;
×
230
        }
231

232
        if (disposing)
15✔
233
        {
234
            cancellationTokenSource.Cancel();
15✔
235
            //While we wait for the background thread to exit lets do some cleanup
236

237
            runspace.Dispose();
15✔
238

239
            if (!backgroundThread.Join(200))
15✔
240
            {
241
                //todo: log that we failed to shutdown background thread!
242
            }
243
        }
244

245
        disposed = true;
15✔
246
    }
15✔
247
}
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