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

luttje / Key2Joy / 6557152774

18 Oct 2023 06:36AM UTC coverage: 52.519% (+39.8%) from 12.718%
6557152774

push

github

web-flow
Adding more tests, fixing bugs in the process (#48)

* Add config manager tests

* legacy config and mapping profile tests (should fix #42)

* remove comment, problem was caused by transfer across appdomain (or to/fro scripting environment)

* Test core functionality #48 + includes minor refactoring to be able to test + added docs

* Add interop tests + implement and test async test utility (refactors away from singletons)

* fix not all tests running in workflow

* config and interop tests

* Refactor and allow mocking global input hook class

* add capture action test

* Make Execute override optional for script only methods

* add dependency injection + refactor and try test gamepad service

* Refactor config singleton to using dependency injection

* add tests for scripting

* add tests for plugin set + fix plugin showing as loaded even if checksum match failed

* fix tests failing because it relied on config exist (I guess the test order was accidentally correct earlier, this means we should really fix cleanup so we catch this sooner)

* refactor docs code + fix wrong enum summary

* refactor docs builder and start testing it a bit

* fix cmd project structure

* ignore designer files in tests

* cleanup and refactor UI code + show latest version in help

* truncate listview action column

* allow user config to minimize app when pressing X (defaults to shut down app) resolves #45

696 of 1757 branches covered (0.0%)

Branch coverage included in aggregate %.

4597 of 4597 new or added lines in 138 files covered. (100.0%)

3619 of 6459 relevant lines covered (56.03%)

17089.01 hits per line

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

87.25
/Core/Key2Joy.Core/Plugins/PluginSet.cs
1
using System;
2
using System.Collections.Generic;
3
using System.IO;
4
using CommonServiceLocator;
5
using Key2Joy.Config;
6
using Key2Joy.Contracts;
7
using Key2Joy.Contracts.Mapping;
8
using Key2Joy.Contracts.Mapping.Actions;
9
using Key2Joy.Contracts.Mapping.Triggers;
10
using Key2Joy.Contracts.Plugins;
11
using Key2Joy.Mapping;
12
using Key2Joy.Mapping.Actions;
13
using Key2Joy.Mapping.Triggers;
14
using static System.Windows.Forms.AxHost;
15

16
namespace Key2Joy.Plugins;
17

18
[ExposesEnumeration(typeof(LowLevelInput.Mouse.MoveType))]
19
[ExposesEnumeration(typeof(LowLevelInput.Mouse.Buttons))]
20
[ExposesEnumeration(typeof(SimWinInput.GamePadControl))]
21
[ExposesEnumeration(typeof(LowLevelInput.PressState))]
22
[ExposesEnumeration(typeof(LowLevelInput.Simulator.GamePadStick))]
23
[ExposesEnumeration(typeof(Mapping.Actions.Logic.AppCommand))]
24
[ExposesEnumeration(typeof(LowLevelInput.KeyboardKey))]
25
public class PluginSet : IDisposable
26
{
27
    public string PluginsFolder { get; private set; }
7✔
28

29
    private readonly Dictionary<string, PluginLoadState> pluginLoadStates = new();
6✔
30
    public IReadOnlyDictionary<string, PluginLoadState> AllPluginLoadStates => this.pluginLoadStates;
5✔
31

32
    private readonly string[] pluginDirectoriesPaths;
33
    public IReadOnlyList<string> PluginAssemblyPaths => this.pluginDirectoriesPaths;
×
34
    private readonly IList<PluginHostProxy> proxiesToDispose = new List<PluginHostProxy>();
6✔
35

36
    /**
37
     * Plugin customizations
38
     */
39
    private readonly List<MappingTypeFactory<AbstractAction>> actionFactories = new();
6✔
40
    private readonly List<MappingTypeFactory<AbstractTrigger>> triggerFactories = new();
6✔
41
    private readonly List<MappingControlFactory> mappingControlFactories = new();
6✔
42
    private readonly List<ExposedEnumeration> exposedEnumerations = new();
6✔
43

44
    private readonly IConfigManager configManager;
45

46
    /// <summary>
47
    /// Loads plugins from the specified directory
48
    /// </summary>
49
    /// <param name="pluginDirectoriesPaths">The absolute path to the directory containing the plugins</param>
50
    /// <returns></returns>
51
    public PluginSet(string pluginDirectoriesPaths)
6✔
52
    {
53
        if (!Path.IsPathRooted(pluginDirectoriesPaths))
6!
54
        {
55
            throw new ArgumentException("Plugin directory path must be absolute", nameof(pluginDirectoriesPaths));
×
56
        }
57

58
        this.configManager = ServiceLocator.Current.GetInstance<IConfigManager>();
6✔
59

60
        this.PluginsFolder = pluginDirectoriesPaths;
6✔
61

62
        if (!string.IsNullOrWhiteSpace(pluginDirectoriesPaths)
6!
63
            && !Directory.Exists(pluginDirectoriesPaths))
6✔
64
        {
65
            Directory.CreateDirectory(pluginDirectoriesPaths);
×
66
        }
67

68
        this.pluginDirectoriesPaths = Directory.GetDirectories(pluginDirectoriesPaths);
6✔
69
    }
6✔
70

71
    public void LoadAll()
72
    {
73
        foreach (var pluginDirectoryPath in this.pluginDirectoriesPaths)
20✔
74
        {
75
            var pluginAssemblyName = Path.GetFileName(pluginDirectoryPath);
5✔
76
            var pluginAssemblyFileName = $"{pluginAssemblyName}.dll";
5✔
77
            var pluginAssemblyPath = Path.Combine(pluginDirectoryPath, pluginAssemblyFileName);
5✔
78
            var expectedChecksum = this.configManager.GetExpectedPluginChecksum(pluginAssemblyPath);
5✔
79

80
            if (this.configManager.IsPluginEnabled(pluginAssemblyPath))
5✔
81
            {
82
                var plugin = this.LoadPlugin(pluginAssemblyPath, expectedChecksum);
3✔
83

84
                if (plugin != null)
3✔
85
                {
86
                    this.AddPluginState(PluginLoadStates.Loaded, pluginAssemblyPath, null, plugin);
2✔
87
                }
88
            }
89
            else
90
            {
91
                this.AddPluginState(
2✔
92
                    PluginLoadStates.NotLoaded,
2✔
93
                    pluginAssemblyPath,
2✔
94
                    "Plugin disabled. Enable it if you trust the author."
2✔
95
                );
2✔
96
            }
97
        }
98
    }
5✔
99

100
    public void RefreshPluginTypes()
101
    {
102
        ActionsRepository.Buffer(this.actionFactories);
2✔
103
        TriggersRepository.Buffer(this.triggerFactories);
2✔
104
        MappingControlRepository.Buffer(this.mappingControlFactories);
2✔
105
        ExposedEnumerationRepository.Buffer(this.exposedEnumerations);
2✔
106
    }
2✔
107

108
    public PluginHostProxy LoadPlugin(string pluginAssemblyPath, string expectedChecksum = null)
109
    {
110
        var pluginAssemblyName = Path.GetFileName(pluginAssemblyPath).Replace(".dll", "");
3✔
111
        PluginHostProxy pluginHost = new(pluginAssemblyPath, pluginAssemblyName);
3✔
112
        string loadedChecksum;
113

114
        try
115
        {
116
            pluginHost.LoadPlugin(out loadedChecksum, expectedChecksum);
3✔
117

118
            this.actionFactories.AddRange(pluginHost.GetActionFactories());
2✔
119
            this.triggerFactories.AddRange(pluginHost.GetTriggerFactories());
2✔
120
            this.mappingControlFactories.AddRange(pluginHost.GetMappingControlFactories());
2✔
121
            this.exposedEnumerations.AddRange(pluginHost.GetExposedEnumerations());
2✔
122
        }
2✔
123
        catch (PluginLoadException ex)
1✔
124
        {
125
            Output.WriteLine(ex);
1✔
126
            pluginHost.Dispose();
1✔
127

128
            this.AddPluginState(
1✔
129
                PluginLoadStates.FailedToLoad,
1✔
130
                pluginAssemblyPath,
1✔
131
                ex.Message
1✔
132
            );
1✔
133

134
            return null;
1✔
135
        }
136

137
        this.AddPluginState(PluginLoadStates.Loaded, pluginAssemblyPath, null, pluginHost);
2✔
138

139
        this.configManager.SetPluginEnabled(pluginAssemblyPath, loadedChecksum);
2✔
140

141
        this.proxiesToDispose.Add(pluginHost);
2✔
142

143
        return pluginHost;
2✔
144
    }
1✔
145

146
    /// <summary>
147
    /// Disables the plugin for next load. Note that this doesnt unload resources already
148
    /// started by the plugin
149
    /// TODO: Fully unload plugin
150
    /// </summary>
151
    /// <param name="pluginAssemblyPath"></param>
152
    public void DisablePlugin(string pluginAssemblyPath)
153
        => this.configManager.SetPluginEnabled(pluginAssemblyPath, null);
1✔
154

155
    internal void AddPluginState(PluginLoadStates state, string pluginAssemblyPath, string errorMessage, PluginHostProxy loadedPlugin = null)
156
    {
157
        if (this.pluginLoadStates.TryGetValue(pluginAssemblyPath, out var loadState))
7✔
158
        {
159
            loadState.LoadState = state;
2✔
160
            loadState.SetPluginHost(loadedPlugin);
2✔
161
            loadState.LoadErrorMessage = errorMessage ?? string.Empty;
2✔
162
            return;
2✔
163
        }
164

165
        loadState = new PluginLoadState(pluginAssemblyPath)
5✔
166
        {
5✔
167
            LoadState = state,
5✔
168
            LoadErrorMessage = errorMessage
5✔
169
        };
5✔
170
        loadState.SetPluginHost(loadedPlugin);
5✔
171

172
        this.pluginLoadStates.Add(
5✔
173
            pluginAssemblyPath,
5✔
174
            loadState
5✔
175
        );
5✔
176
    }
5✔
177

178
    public void Dispose()
179
    {
180
        foreach (var keyValuePair in this.pluginLoadStates)
×
181
        {
182
            var pluginLoadState = keyValuePair.Value;
×
183
            pluginLoadState.PluginHost?.Dispose();
×
184
        }
185
    }
×
186
}
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