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

Ortham / libloadorder / 23689330707

28 Mar 2026 04:16PM UTC coverage: 92.975% (+0.2%) from 92.789%
23689330707

push

github

Ortham
Deactivate BlueprintShips plugins when their base plugin is deactivated

When deactivating a plugin, check if there's a corresponding BlueprintShips plugin that's not explicitly active and which isn't implicitly active for any other reason (e.g. game config, another base plugin with a different file extension). If found, deactivate that BlueprintShips plugin.

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

56 existing lines in 7 files now uncovered.

9715 of 10449 relevant lines covered (92.98%)

3134320.06 hits per line

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

97.71
/src/load_order/readable.rs
1
/*
2
 * This file is part of libloadorder
3
 *
4
 * Copyright (C) 2017 Oliver Hamlet
5
 *
6
 * libloadorder is free software: you can redistribute it and/or modify
7
 * it under the terms of the GNU General Public License as published by
8
 * the Free Software Foundation, either version 3 of the License, or
9
 * (at your option) any later version.
10
 *
11
 * libloadorder is distributed in the hope that it will be useful,
12
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
 * GNU General Public License for more details.
15
 *
16
 * You should have received a copy of the GNU General Public License
17
 * along with libloadorder. If not, see <http://www.gnu.org/licenses/>.
18
 */
19
use crate::game_settings::GameSettings;
20
use crate::plugin::Plugin;
21

22
pub trait ReadableLoadOrderBase {
23
    fn plugins(&self) -> &[Plugin];
24

25
    fn game_settings_base(&self) -> &GameSettings;
26

27
    fn find_plugin(&self, plugin_name: &str) -> Option<&Plugin> {
690✔
28
        self.plugins().iter().find(|p| p.name_matches(plugin_name))
110,678✔
29
    }
690✔
30

31
    fn find_plugin_and_index(&self, plugin_name: &str) -> Option<(usize, &Plugin)> {
6,588✔
32
        self.plugins()
6,588✔
33
            .iter()
6,588✔
34
            .enumerate()
6,588✔
35
            .find(|(_, p)| p.name_matches(plugin_name))
1,283,744✔
36
    }
6,588✔
37
}
38

39
pub trait ReadableLoadOrder {
40
    fn game_settings(&self) -> &GameSettings;
41

42
    fn plugin_names(&self) -> Vec<&str>;
43

44
    fn index_of(&self, plugin_name: &str) -> Option<usize>;
45

46
    fn plugin_at(&self, index: usize) -> Option<&str>;
47

48
    fn active_plugin_names(&self) -> Vec<&str>;
49

50
    fn is_active(&self, plugin_name: &str) -> bool;
51
}
52

53
impl<T: ReadableLoadOrderBase> ReadableLoadOrder for T {
54
    fn game_settings(&self) -> &GameSettings {
138,708✔
55
        self.game_settings_base()
138,708✔
56
    }
138,708✔
57

58
    fn plugin_names(&self) -> Vec<&str> {
120✔
59
        self.plugins().iter().map(Plugin::name).collect()
120✔
60
    }
120✔
61

62
    fn index_of(&self, plugin_name: &str) -> Option<usize> {
20,240✔
63
        self.plugins()
20,240✔
64
            .iter()
20,240✔
65
            .position(|p| p.name_matches(plugin_name))
102,267,890✔
66
    }
20,240✔
67

68
    fn plugin_at(&self, index: usize) -> Option<&str> {
6✔
69
        self.plugins().get(index).map(Plugin::name)
6✔
70
    }
6✔
71

72
    fn active_plugin_names(&self) -> Vec<&str> {
100✔
73
        self.plugins()
100✔
74
            .iter()
100✔
75
            .filter(|p| p.is_active())
26,082✔
76
            .map(Plugin::name)
100✔
77
            .collect()
100✔
78
    }
100✔
79

80
    fn is_active(&self, plugin_name: &str) -> bool {
672✔
81
        self.find_plugin(plugin_name).is_some_and(Plugin::is_active)
672✔
82
    }
672✔
83
}
84

85
#[cfg(test)]
86
mod tests {
87
    use super::*;
88

89
    use std::path::Path;
90

91
    use tempfile::tempdir;
92

93
    use crate::enums::GameId;
94
    use crate::load_order::tests::{game_settings_for_test, mock_game_files};
95
    use crate::plugin::ActiveState;
96
    use crate::tests::copy_to_test_dir;
97

98
    struct TestLoadOrder {
99
        game_settings: GameSettings,
100
        plugins: Vec<Plugin>,
101
    }
102

103
    impl ReadableLoadOrderBase for TestLoadOrder {
104
        fn game_settings_base(&self) -> &GameSettings {
×
105
            &self.game_settings
×
UNCOV
106
        }
×
107

108
        fn plugins(&self) -> &[Plugin] {
26✔
109
            &self.plugins
26✔
110
        }
26✔
111
    }
112

113
    fn prepare(game_dir: &Path) -> TestLoadOrder {
26✔
114
        let mut game_settings = game_settings_for_test(GameId::Oblivion, game_dir);
26✔
115
        mock_game_files(&mut game_settings);
26✔
116

117
        let plugins = vec![
26✔
118
            Plugin::with_active("Blank.esp", &game_settings, ActiveState::ExplicitlyActive)
26✔
119
                .unwrap(),
26✔
120
            Plugin::new("Blank - Different.esp", &game_settings).unwrap(),
26✔
121
        ];
122

123
        TestLoadOrder {
26✔
124
            game_settings,
26✔
125
            plugins,
26✔
126
        }
26✔
127
    }
26✔
128

129
    fn prepare_with_ghosted_plugin(game_dir: &Path) -> TestLoadOrder {
4✔
130
        let mut load_order = prepare(game_dir);
4✔
131

132
        copy_to_test_dir(
4✔
133
            "Blank - Different.esm",
4✔
134
            "Blank - Different.esm.ghost",
4✔
135
            &load_order.game_settings,
4✔
136
        );
137
        load_order.plugins.insert(
4✔
138
            0,
139
            Plugin::new("Blank - Different.esm.ghost", &load_order.game_settings).unwrap(),
4✔
140
        );
141

142
        load_order
4✔
143
    }
4✔
144

145
    #[test]
146
    fn plugin_names_should_return_filenames_for_plugins_in_load_order() {
2✔
147
        let tmp_dir = tempdir().unwrap();
2✔
148
        let load_order = prepare(tmp_dir.path());
2✔
149

150
        let expected_plugin_names = vec!["Blank.esp", "Blank - Different.esp"];
2✔
151
        assert_eq!(expected_plugin_names, load_order.plugin_names());
2✔
152
    }
2✔
153

154
    #[test]
155
    fn plugin_names_should_return_unghosted_filenames() {
2✔
156
        let tmp_dir = tempdir().unwrap();
2✔
157
        let load_order = prepare_with_ghosted_plugin(tmp_dir.path());
2✔
158

159
        let expected_plugin_names = vec![
2✔
160
            "Blank - Different.esm",
161
            "Blank.esp",
2✔
162
            "Blank - Different.esp",
2✔
163
        ];
164
        assert_eq!(expected_plugin_names, load_order.plugin_names());
2✔
165
    }
2✔
166

167
    #[test]
168
    fn index_of_should_return_none_if_the_plugin_is_not_in_the_load_order() {
2✔
169
        let tmp_dir = tempdir().unwrap();
2✔
170
        let load_order = prepare(tmp_dir.path());
2✔
171

172
        assert!(load_order.index_of("Blank.esm").is_none());
2✔
173
    }
2✔
174

175
    #[test]
176
    fn index_of_should_return_some_index_if_the_plugin_is_in_the_load_order() {
2✔
177
        let tmp_dir = tempdir().unwrap();
2✔
178
        let load_order = prepare(tmp_dir.path());
2✔
179

180
        assert_eq!(0, load_order.index_of("Blank.esp").unwrap());
2✔
181
    }
2✔
182

183
    #[test]
184
    fn index_of_should_be_case_insensitive() {
2✔
185
        let tmp_dir = tempdir().unwrap();
2✔
186
        let load_order = prepare(tmp_dir.path());
2✔
187

188
        assert_eq!(0, load_order.index_of("blank.esp").unwrap());
2✔
189
    }
2✔
190

191
    #[test]
192
    fn plugin_at_should_return_none_if_given_an_out_of_bounds_index() {
2✔
193
        let tmp_dir = tempdir().unwrap();
2✔
194
        let load_order = prepare(tmp_dir.path());
2✔
195

196
        assert!(load_order.plugin_at(3).is_none());
2✔
197
    }
2✔
198

199
    #[test]
200
    fn plugin_at_should_return_some_filename_if_given_an_in_bounds_index() {
2✔
201
        let tmp_dir = tempdir().unwrap();
2✔
202
        let load_order = prepare(tmp_dir.path());
2✔
203

204
        assert_eq!("Blank.esp", load_order.plugin_at(0).unwrap());
2✔
205
    }
2✔
206

207
    #[test]
208
    fn plugin_at_should_return_some_unghosted_filename() {
2✔
209
        let tmp_dir = tempdir().unwrap();
2✔
210
        let load_order = prepare_with_ghosted_plugin(tmp_dir.path());
2✔
211

212
        assert_eq!("Blank - Different.esm", load_order.plugin_at(0).unwrap());
2✔
213
    }
2✔
214

215
    #[test]
216
    fn active_plugin_names_should_return_filenames_for_active_plugins_in_load_order() {
2✔
217
        let tmp_dir = tempdir().unwrap();
2✔
218
        let load_order = prepare(tmp_dir.path());
2✔
219

220
        let expected_plugin_names = vec!["Blank.esp"];
2✔
221
        assert_eq!(expected_plugin_names, load_order.active_plugin_names());
2✔
222
    }
2✔
223

224
    #[test]
225
    fn is_active_should_return_false_for_an_inactive_plugin() {
2✔
226
        let tmp_dir = tempdir().unwrap();
2✔
227
        let load_order = prepare(tmp_dir.path());
2✔
228

229
        assert!(!load_order.is_active("Blank - Different.esp"));
2✔
230
    }
2✔
231

232
    #[test]
233
    fn is_active_should_return_false_a_plugin_not_in_the_load_order() {
2✔
234
        let tmp_dir = tempdir().unwrap();
2✔
235
        let load_order = prepare(tmp_dir.path());
2✔
236

237
        assert!(!load_order.is_active("missing.esp"));
2✔
238
    }
2✔
239

240
    #[test]
241
    fn is_active_should_return_true_for_an_active_plugin() {
2✔
242
        let tmp_dir = tempdir().unwrap();
2✔
243
        let load_order = prepare(tmp_dir.path());
2✔
244

245
        assert!(load_order.is_active("Blank.esp"));
2✔
246
    }
2✔
247

248
    #[test]
249
    fn is_active_should_be_case_insensitive() {
2✔
250
        let tmp_dir = tempdir().unwrap();
2✔
251
        let load_order = prepare(tmp_dir.path());
2✔
252

253
        assert!(load_order.is_active("blank.esp"));
2✔
254
    }
2✔
255
}
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