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

Ortham / libloadorder / 6292042269

24 Sep 2023 07:44PM UTC coverage: 88.152% (+0.01%) from 88.141%
6292042269

push

github

Ortham
[WIP] Add support for plugins loaded from ini files

The sTestFile1 through sTestFile10 ini file properties can be used to
activate plugins in most supported games.

libloadorder treats plugins activated this way as implicitly active, and
because they are not hardcoded to load early, now distinguishes between
plugins that are implicitly active and those that load early.

As part of this, the existing Oblivion.ini parsing that checks
bUseMyGamesDirectory now correctly requires that the property is in the
ini file's [General] section.

This also adds a lo_get_early_loading_plugins() FFI function, as the set
of implicitly active plugins may now be a superset of the set of early
loaders that includes plugins with non-hardcoded positions.

407 of 407 new or added lines in 7 files covered. (100.0%)

5796 of 6575 relevant lines covered (88.15%)

78916.66 hits per line

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

97.58
/src/load_order/asterisk_based.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 std::collections::HashSet;
20
use std::fs::File;
21
use std::io::{BufWriter, Write};
22

23
use unicase::eq;
24

25
use super::mutable::{generic_insert_position, hoist_masters, read_plugin_names, MutableLoadOrder};
26
use super::readable::{ReadableLoadOrder, ReadableLoadOrderBase};
27
use super::strict_encode;
28
use super::writable::{
29
    activate, add, create_parent_dirs, deactivate, remove, set_active_plugins, WritableLoadOrder,
30
};
31
use crate::enums::{Error, GameId};
32
use crate::game_settings::GameSettings;
33
use crate::plugin::{trim_dot_ghost, Plugin};
34

35
#[derive(Clone, Debug)]
×
36
pub struct AsteriskBasedLoadOrder {
37
    game_settings: GameSettings,
38
    plugins: Vec<Plugin>,
39
}
40

41
impl AsteriskBasedLoadOrder {
42
    pub fn new(game_settings: GameSettings) -> Self {
×
43
        Self {
×
44
            game_settings,
×
45
            plugins: Vec::new(),
×
46
        }
×
47
    }
×
48

49
    fn read_from_active_plugins_file(&self) -> Result<Vec<(String, bool)>, Error> {
20✔
50
        if self.ignore_active_plugins_file() {
20✔
51
            Ok(Vec::new())
×
52
        } else {
53
            read_plugin_names(
20✔
54
                self.game_settings().active_plugins_file(),
20✔
55
                owning_plugin_line_mapper,
20✔
56
            )
20✔
57
        }
58
    }
20✔
59

60
    fn ignore_active_plugins_file(&self) -> bool {
24✔
61
        // Fallout 4 ignores plugins.txt if there are any sTestFile plugins listed in the ini files. The implicitly active plugins are the early loading plugins plus test file plugins.
24✔
62
        (self.game_settings.id() == GameId::Fallout4
24✔
63
            || self.game_settings.id() == GameId::Fallout4VR)
23✔
64
            && self.game_settings.implicitly_active_plugins().len()
1✔
65
                > self.game_settings.early_loading_plugins().len()
1✔
66
    }
24✔
67
}
68

69
impl ReadableLoadOrderBase for AsteriskBasedLoadOrder {
70
    fn game_settings_base(&self) -> &GameSettings {
21,612✔
71
        &self.game_settings
21,612✔
72
    }
21,612✔
73

74
    fn plugins(&self) -> &[Plugin] {
30,054✔
75
        &self.plugins
30,054✔
76
    }
30,054✔
77
}
78

79
impl MutableLoadOrder for AsteriskBasedLoadOrder {
80
    fn plugins_mut(&mut self) -> &mut Vec<Plugin> {
19,479✔
81
        &mut self.plugins
19,479✔
82
    }
19,479✔
83

84
    fn insert_position(&self, plugin: &Plugin) -> Option<usize> {
10,646✔
85
        if self.game_settings().loads_early(plugin.name()) {
10,646✔
86
            if self.plugins().is_empty() {
24✔
87
                return None;
3✔
88
            }
21✔
89

21✔
90
            let mut loaded_plugin_count = 0;
21✔
91
            for plugin_name in self.game_settings().early_loading_plugins() {
31✔
92
                if eq(plugin.name(), plugin_name) {
31✔
93
                    return Some(loaded_plugin_count);
21✔
94
                }
10✔
95

10✔
96
                if self.index_of(plugin_name).is_some() {
10✔
97
                    loaded_plugin_count += 1;
3✔
98
                }
7✔
99
            }
100
        }
10,622✔
101

102
        generic_insert_position(self.plugins(), plugin)
10,622✔
103
    }
10,646✔
104
}
105

106
impl WritableLoadOrder for AsteriskBasedLoadOrder {
107
    fn game_settings_mut(&mut self) -> &mut GameSettings {
×
108
        &mut self.game_settings
×
109
    }
×
110

111
    fn load(&mut self) -> Result<(), Error> {
20✔
112
        self.plugins_mut().clear();
20✔
113

114
        let plugin_tuples = self.read_from_active_plugins_file()?;
20✔
115
        let filenames = self.find_plugins_sorted();
20✔
116

20✔
117
        self.load_unique_plugins(plugin_tuples, filenames);
20✔
118
        hoist_masters(&mut self.plugins)?;
20✔
119

120
        self.add_implicitly_active_plugins()?;
20✔
121

122
        Ok(())
20✔
123
    }
20✔
124

125
    fn save(&mut self) -> Result<(), Error> {
126
        create_parent_dirs(self.game_settings().active_plugins_file())?;
4✔
127

128
        let file = File::create(self.game_settings().active_plugins_file())?;
4✔
129
        let mut writer = BufWriter::new(file);
4✔
130
        for plugin in self.plugins() {
14✔
131
            if self.game_settings().loads_early(plugin.name()) {
14✔
132
                // Skip early loading plugins, but not implicitly active plugins
133
                // as they may need load order positions defined.
134
                continue;
4✔
135
            }
10✔
136

10✔
137
            if plugin.is_active() {
10✔
138
                write!(writer, "*")?;
4✔
139
            }
6✔
140
            writer.write_all(&strict_encode(plugin.name())?)?;
10✔
141
            writeln!(writer)?;
9✔
142
        }
143

144
        Ok(())
3✔
145
    }
4✔
146

147
    fn add(&mut self, plugin_name: &str) -> Result<usize, Error> {
×
148
        add(self, plugin_name)
×
149
    }
×
150

151
    fn remove(&mut self, plugin_name: &str) -> Result<(), Error> {
×
152
        remove(self, plugin_name)
×
153
    }
×
154

155
    fn set_load_order(&mut self, plugin_names: &[&str]) -> Result<(), Error> {
7✔
156
        let is_game_master_first = plugin_names
7✔
157
            .first()
7✔
158
            .map(|name| eq(*name, self.game_settings().master_file()))
7✔
159
            .unwrap_or(false);
7✔
160
        if !is_game_master_first {
7✔
161
            return Err(Error::GameMasterMustLoadFirst);
2✔
162
        }
5✔
163

5✔
164
        // Check that all early loading plugins that are present load in
5✔
165
        // their hardcoded order.
5✔
166
        let mut missing_plugins_count = 0;
5✔
167
        for (i, plugin_name) in self
22✔
168
            .game_settings()
5✔
169
            .early_loading_plugins()
5✔
170
            .iter()
5✔
171
            .enumerate()
5✔
172
        {
173
            match plugin_names.iter().position(|n| eq(*n, plugin_name)) {
99✔
174
                Some(pos) => {
7✔
175
                    if pos != i - missing_plugins_count {
7✔
176
                        return Err(Error::GameMasterMustLoadFirst);
1✔
177
                    }
6✔
178
                }
179
                None => missing_plugins_count += 1,
15✔
180
            }
181
        }
182

183
        self.replace_plugins(plugin_names)
4✔
184
    }
7✔
185

186
    fn set_plugin_index(&mut self, plugin_name: &str, position: usize) -> Result<usize, Error> {
3✔
187
        if position != 0
3✔
188
            && !self.plugins().is_empty()
2✔
189
            && eq(plugin_name, self.game_settings().master_file())
2✔
190
        {
191
            return Err(Error::GameMasterMustLoadFirst);
1✔
192
        }
2✔
193
        if position == 0 && !eq(plugin_name, self.game_settings().master_file()) {
2✔
194
            return Err(Error::GameMasterMustLoadFirst);
1✔
195
        }
1✔
196

1✔
197
        self.move_or_insert_plugin_with_index(plugin_name, position)
1✔
198
    }
3✔
199

200
    fn is_self_consistent(&self) -> Result<bool, Error> {
1✔
201
        Ok(true)
1✔
202
    }
1✔
203

204
    /// An asterisk-based load order can be ambiguous if there are installed
205
    /// plugins that don't exist in the active plugins file.
206
    fn is_ambiguous(&self) -> Result<bool, Error> {
4✔
207
        let mut set: HashSet<String> = HashSet::new();
4✔
208

4✔
209
        // Read plugins from the active plugins file. A set of plugin names is
4✔
210
        // more useful than the returned vec, so insert into the set during the
4✔
211
        // line mapping and then discard the line.
4✔
212
        if !self.ignore_active_plugins_file() {
4✔
213
            read_plugin_names(self.game_settings().active_plugins_file(), |line| {
12✔
214
                plugin_line_mapper(line).and_then::<(), _>(|(name, _)| {
12✔
215
                    set.insert(trim_dot_ghost(name).to_lowercase());
12✔
216
                    None
12✔
217
                })
12✔
218
            })?;
12✔
219
        }
×
220

221
        // Check if all loaded plugins aside from early loading plugins
222
        // (which don't get written to the active plugins file) are named in the
223
        // set.
224
        let all_plugins_listed = self
4✔
225
            .plugins
4✔
226
            .iter()
4✔
227
            .filter(|plugin| !self.game_settings().loads_early(plugin.name()))
13✔
228
            .all(|plugin| set.contains(&plugin.name().to_lowercase()));
8✔
229

4✔
230
        Ok(!all_plugins_listed)
4✔
231
    }
4✔
232

233
    fn activate(&mut self, plugin_name: &str) -> Result<(), Error> {
4✔
234
        activate(self, plugin_name)
4✔
235
    }
4✔
236

237
    fn deactivate(&mut self, plugin_name: &str) -> Result<(), Error> {
×
238
        deactivate(self, plugin_name)
×
239
    }
×
240

241
    fn set_active_plugins(&mut self, active_plugin_names: &[&str]) -> Result<(), Error> {
3✔
242
        set_active_plugins(self, active_plugin_names)
3✔
243
    }
3✔
244
}
245

246
fn plugin_line_mapper(line: &str) -> Option<(&str, bool)> {
10,557✔
247
    if line.is_empty() || line.starts_with('#') {
10,557✔
248
        None
×
249
    } else if line.as_bytes()[0] == b'*' {
10,557✔
250
        Some((&line[1..], true))
10,556✔
251
    } else {
252
        Some((line, false))
1✔
253
    }
254
}
10,557✔
255

256
fn owning_plugin_line_mapper(line: &str) -> Option<(String, bool)> {
10,545✔
257
    plugin_line_mapper(line).map(|(name, active)| (name.to_owned(), active))
10,545✔
258
}
10,545✔
259

260
#[cfg(test)]
261
mod tests {
262
    use super::*;
263

264
    use crate::enums::GameId;
265
    use crate::load_order::tests::*;
266
    use crate::tests::{copy_to_dir, copy_to_test_dir};
267
    use filetime::{set_file_times, FileTime};
268
    use std::fs::{create_dir_all, remove_dir_all, File};
269
    use std::io;
270
    use std::io::{BufRead, BufReader};
271
    use std::path::Path;
272
    use tempfile::tempdir;
273

274
    fn prepare(game_id: GameId, game_dir: &Path) -> AsteriskBasedLoadOrder {
46✔
275
        let (game_settings, plugins) = mock_game_files(game_id, game_dir);
46✔
276
        AsteriskBasedLoadOrder {
46✔
277
            game_settings,
46✔
278
            plugins,
46✔
279
        }
46✔
280
    }
46✔
281

282
    fn prepare_bulk_plugins(game_settings: &GameSettings) -> Vec<String> {
3✔
283
        let mut plugins: Vec<String> = vec![game_settings.master_file().to_string()];
3✔
284
        plugins.extend((0..260).map(|i| format!("Blank{}.esm", i)));
780✔
285
        plugins.extend((0..5000).map(|i| format!("Blank{}.esl", i)));
15,000✔
286

287
        for plugin in &plugins {
15,786✔
288
            copy_to_test_dir("Blank - Different.esm", &plugin, game_settings);
15,783✔
289
        }
15,783✔
290

291
        write_active_plugins_file(game_settings, &plugins);
3✔
292

3✔
293
        plugins
3✔
294
    }
3✔
295

296
    #[test]
1✔
297
    fn insert_position_should_return_none_for_the_game_master_if_no_plugins_are_loaded() {
1✔
298
        let tmp_dir = tempdir().unwrap();
1✔
299
        let mut load_order = prepare(GameId::SkyrimSE, &tmp_dir.path());
1✔
300

1✔
301
        load_order.plugins_mut().clear();
1✔
302

1✔
303
        let plugin = Plugin::new("Skyrim.esm", &load_order.game_settings()).unwrap();
1✔
304
        let position = load_order.insert_position(&plugin);
1✔
305

1✔
306
        assert!(position.is_none());
1✔
307
    }
1✔
308

309
    #[test]
1✔
310
    fn insert_position_should_return_the_hardcoded_index_of_an_implicitly_active_plugin() {
1✔
311
        let tmp_dir = tempdir().unwrap();
1✔
312
        let mut load_order = prepare(GameId::SkyrimSE, &tmp_dir.path());
1✔
313

1✔
314
        let plugin = Plugin::new("Blank.esm", &load_order.game_settings()).unwrap();
1✔
315
        load_order.plugins_mut().insert(1, plugin);
1✔
316

1✔
317
        copy_to_test_dir("Blank.esm", "HearthFires.esm", &load_order.game_settings());
1✔
318
        let plugin = Plugin::new("HearthFires.esm", &load_order.game_settings()).unwrap();
1✔
319
        let position = load_order.insert_position(&plugin);
1✔
320

1✔
321
        assert_eq!(1, position.unwrap());
1✔
322
    }
1✔
323

324
    #[test]
1✔
325
    fn insert_position_should_not_count_installed_unloaded_implicitly_active_plugins() {
1✔
326
        let tmp_dir = tempdir().unwrap();
1✔
327
        let load_order = prepare(GameId::SkyrimSE, &tmp_dir.path());
1✔
328

1✔
329
        copy_to_test_dir("Blank.esm", "Update.esm", &load_order.game_settings());
1✔
330
        copy_to_test_dir("Blank.esm", "HearthFires.esm", &load_order.game_settings());
1✔
331
        let plugin = Plugin::new("HearthFires.esm", &load_order.game_settings()).unwrap();
1✔
332
        let position = load_order.insert_position(&plugin);
1✔
333

1✔
334
        assert_eq!(1, position.unwrap());
1✔
335
    }
1✔
336

337
    #[test]
1✔
338
    fn insert_position_should_return_none_if_given_a_non_master_plugin() {
1✔
339
        let tmp_dir = tempdir().unwrap();
1✔
340
        let load_order = prepare(GameId::SkyrimSE, &tmp_dir.path());
1✔
341

1✔
342
        let plugin =
1✔
343
            Plugin::new("Blank - Master Dependent.esp", &load_order.game_settings()).unwrap();
1✔
344
        let position = load_order.insert_position(&plugin);
1✔
345

1✔
346
        assert_eq!(None, position);
1✔
347
    }
1✔
348

349
    #[test]
1✔
350
    fn insert_position_should_return_the_first_non_master_plugin_index_if_given_a_master_plugin() {
1✔
351
        let tmp_dir = tempdir().unwrap();
1✔
352
        let load_order = prepare(GameId::SkyrimSE, &tmp_dir.path());
1✔
353

1✔
354
        let plugin = Plugin::new("Blank.esm", &load_order.game_settings()).unwrap();
1✔
355
        let position = load_order.insert_position(&plugin);
1✔
356

1✔
357
        assert_eq!(1, position.unwrap());
1✔
358
    }
1✔
359

360
    #[test]
1✔
361
    fn insert_position_should_return_none_if_no_non_masters_are_present() {
1✔
362
        let tmp_dir = tempdir().unwrap();
1✔
363
        let mut load_order = prepare(GameId::SkyrimSE, &tmp_dir.path());
1✔
364

1✔
365
        // Remove non-master plugins from the load order.
1✔
366
        load_order.plugins_mut().retain(|p| p.is_master_file());
3✔
367

1✔
368
        let plugin = Plugin::new("Blank.esm", &load_order.game_settings()).unwrap();
1✔
369
        let position = load_order.insert_position(&plugin);
1✔
370

1✔
371
        assert_eq!(None, position);
1✔
372
    }
1✔
373

374
    #[test]
1✔
375
    fn insert_position_should_return_the_first_non_master_index_if_given_a_light_master() {
1✔
376
        let tmp_dir = tempdir().unwrap();
1✔
377
        let mut load_order = prepare(GameId::SkyrimSE, &tmp_dir.path());
1✔
378

1✔
379
        copy_to_test_dir("Blank.esm", "Blank.esl", load_order.game_settings());
1✔
380
        let plugin = Plugin::new("Blank.esl", &load_order.game_settings()).unwrap();
1✔
381

1✔
382
        load_order.plugins_mut().insert(1, plugin);
1✔
383

1✔
384
        let position = load_order.insert_position(&load_order.plugins()[1]);
1✔
385

1✔
386
        assert_eq!(2, position.unwrap());
1✔
387

388
        copy_to_test_dir(
1✔
389
            "Blank.esp",
1✔
390
            "Blank - Different.esl",
1✔
391
            load_order.game_settings(),
1✔
392
        );
1✔
393
        let plugin = Plugin::new("Blank - Different.esl", &load_order.game_settings()).unwrap();
1✔
394

1✔
395
        let position = load_order.insert_position(&plugin);
1✔
396

1✔
397
        assert_eq!(2, position.unwrap());
1✔
398
    }
1✔
399

400
    #[test]
1✔
401
    fn load_should_reload_existing_plugins() {
1✔
402
        let tmp_dir = tempdir().unwrap();
1✔
403
        let mut load_order = prepare(GameId::SkyrimSE, &tmp_dir.path());
1✔
404

1✔
405
        assert!(!load_order.plugins()[1].is_master_file());
1✔
406
        copy_to_test_dir("Blank.esm", "Blank.esp", &load_order.game_settings());
1✔
407
        let plugin_path = load_order
1✔
408
            .game_settings()
1✔
409
            .plugins_directory()
1✔
410
            .join("Blank.esp");
1✔
411
        set_file_times(&plugin_path, FileTime::zero(), FileTime::zero()).unwrap();
1✔
412

1✔
413
        load_order.load().unwrap();
1✔
414

1✔
415
        assert!(load_order.plugins()[1].is_master_file());
1✔
416
    }
1✔
417

418
    #[test]
1✔
419
    fn load_should_remove_plugins_that_fail_to_load() {
1✔
420
        let tmp_dir = tempdir().unwrap();
1✔
421
        let mut load_order = prepare(GameId::SkyrimSE, &tmp_dir.path());
1✔
422

1✔
423
        assert!(load_order.index_of("Blank.esp").is_some());
1✔
424
        assert!(load_order.index_of("Blank - Different.esp").is_some());
1✔
425

426
        let plugin_path = load_order
1✔
427
            .game_settings()
1✔
428
            .plugins_directory()
1✔
429
            .join("Blank.esp");
1✔
430
        File::create(&plugin_path).unwrap();
1✔
431
        set_file_times(&plugin_path, FileTime::zero(), FileTime::zero()).unwrap();
1✔
432

1✔
433
        let plugin_path = load_order
1✔
434
            .game_settings()
1✔
435
            .plugins_directory()
1✔
436
            .join("Blank - Different.esp");
1✔
437
        File::create(&plugin_path).unwrap();
1✔
438
        set_file_times(&plugin_path, FileTime::zero(), FileTime::zero()).unwrap();
1✔
439

1✔
440
        load_order.load().unwrap();
1✔
441
        assert!(load_order.index_of("Blank.esp").is_none());
1✔
442
        assert!(load_order.index_of("Blank - Different.esp").is_none());
1✔
443
    }
1✔
444

445
    #[test]
1✔
446
    fn load_should_get_load_order_from_active_plugins_file() {
1✔
447
        let tmp_dir = tempdir().unwrap();
1✔
448
        let mut load_order = prepare(GameId::SkyrimSE, &tmp_dir.path());
1✔
449

1✔
450
        write_active_plugins_file(
1✔
451
            load_order.game_settings(),
1✔
452
            &["Blank.esp", "Blank - Master Dependent.esp"],
1✔
453
        );
1✔
454

1✔
455
        load_order.load().unwrap();
1✔
456

1✔
457
        let expected_filenames = vec![
1✔
458
            load_order.game_settings().master_file(),
1✔
459
            "Blank.esm",
1✔
460
            "Blank.esp",
1✔
461
            "Blank - Master Dependent.esp",
1✔
462
            "Blank - Different.esp",
1✔
463
            "Blàñk.esp",
1✔
464
        ];
1✔
465

1✔
466
        assert_eq!(expected_filenames, load_order.plugin_names());
1✔
467
    }
1✔
468

469
    #[test]
1✔
470
    fn load_should_hoist_non_masters_that_masters_depend_on_to_load_before_their_dependents() {
1✔
471
        let tmp_dir = tempdir().unwrap();
1✔
472
        let mut load_order = prepare(GameId::SkyrimSE, &tmp_dir.path());
1✔
473

1✔
474
        // .esm plugins are loaded as ESMs, .esl plugins are loaded as ESMs and
1✔
475
        // ESLs, ignoring their actual flags, so only worth testing a .esp that
1✔
476
        // has the ESM flag set that has another (normal) .esp as a master.
1✔
477

1✔
478
        let plugins_dir = &load_order.game_settings().plugins_directory();
1✔
479
        copy_to_test_dir(
1✔
480
            "Blank - Plugin Dependent.esp",
1✔
481
            "Blank - Plugin Dependent.esp",
1✔
482
            load_order.game_settings(),
1✔
483
        );
1✔
484
        set_master_flag(&plugins_dir.join("Blank - Plugin Dependent.esp"), true).unwrap();
1✔
485

1✔
486
        let expected_filenames = vec![
1✔
487
            "Blank - Master Dependent.esp",
1✔
488
            "Blank - Different.esp",
1✔
489
            "Blàñk.esp",
1✔
490
            "Blank.esp",
1✔
491
            "Skyrim.esm",
1✔
492
            "Blank - Plugin Dependent.esp",
1✔
493
            "Blank.esm",
1✔
494
        ];
1✔
495
        write_active_plugins_file(load_order.game_settings(), &expected_filenames);
1✔
496

1✔
497
        load_order.load().unwrap();
1✔
498

1✔
499
        let expected_filenames = vec![
1✔
500
            "Skyrim.esm",
1✔
501
            "Blank.esp",
1✔
502
            "Blank - Plugin Dependent.esp",
1✔
503
            "Blank.esm",
1✔
504
            "Blank - Master Dependent.esp",
1✔
505
            "Blank - Different.esp",
1✔
506
            "Blàñk.esp",
1✔
507
        ];
1✔
508

1✔
509
        assert_eq!(expected_filenames, load_order.plugin_names());
1✔
510
    }
1✔
511

512
    #[test]
1✔
513
    fn load_should_decode_active_plugins_file_from_windows_1252() {
1✔
514
        let tmp_dir = tempdir().unwrap();
1✔
515
        let mut load_order = prepare(GameId::SkyrimSE, &tmp_dir.path());
1✔
516

1✔
517
        write_active_plugins_file(load_order.game_settings(), &["Blàñk.esp", "Blank.esm"]);
1✔
518

1✔
519
        load_order.load().unwrap();
1✔
520

1✔
521
        let expected_filenames = vec![
1✔
522
            load_order.game_settings().master_file(),
1✔
523
            "Blank.esm",
1✔
524
            "Blàñk.esp",
1✔
525
            "Blank - Different.esp",
1✔
526
            "Blank - Master Dependent.esp",
1✔
527
            "Blank.esp",
1✔
528
        ];
1✔
529

1✔
530
        assert_eq!(expected_filenames, load_order.plugin_names());
1✔
531
    }
1✔
532

533
    #[test]
1✔
534
    fn load_should_handle_crlf_and_lf_in_active_plugins_file() {
1✔
535
        let tmp_dir = tempdir().unwrap();
1✔
536
        let mut load_order = prepare(GameId::SkyrimSE, &tmp_dir.path());
1✔
537

1✔
538
        write_active_plugins_file(load_order.game_settings(), &["Blàñk.esp", "Blank.esm\r"]);
1✔
539

1✔
540
        load_order.load().unwrap();
1✔
541

1✔
542
        let expected_filenames = vec![
1✔
543
            load_order.game_settings().master_file(),
1✔
544
            "Blank.esm",
1✔
545
            "Blàñk.esp",
1✔
546
            "Blank - Different.esp",
1✔
547
            "Blank - Master Dependent.esp",
1✔
548
            "Blank.esp",
1✔
549
        ];
1✔
550

1✔
551
        assert_eq!(expected_filenames, load_order.plugin_names());
1✔
552
    }
1✔
553

554
    #[test]
1✔
555
    fn load_should_ignore_active_plugins_file_lines_starting_with_a_hash() {
1✔
556
        let tmp_dir = tempdir().unwrap();
1✔
557
        let mut load_order = prepare(GameId::SkyrimSE, &tmp_dir.path());
1✔
558

1✔
559
        write_active_plugins_file(
1✔
560
            load_order.game_settings(),
1✔
561
            &["#Blank.esp", "Blàñk.esp", "Blank.esm"],
1✔
562
        );
1✔
563

1✔
564
        load_order.load().unwrap();
1✔
565

1✔
566
        let expected_filenames = vec![
1✔
567
            load_order.game_settings().master_file(),
1✔
568
            "Blank.esm",
1✔
569
            "Blàñk.esp",
1✔
570
            "Blank - Different.esp",
1✔
571
            "Blank - Master Dependent.esp",
1✔
572
            "Blank.esp",
1✔
573
        ];
1✔
574

1✔
575
        assert_eq!(expected_filenames, load_order.plugin_names());
1✔
576
    }
1✔
577

578
    #[test]
1✔
579
    fn load_should_ignore_plugins_in_active_plugins_file_that_are_not_installed() {
1✔
580
        let tmp_dir = tempdir().unwrap();
1✔
581
        let mut load_order = prepare(GameId::SkyrimSE, &tmp_dir.path());
1✔
582

1✔
583
        write_active_plugins_file(
1✔
584
            load_order.game_settings(),
1✔
585
            &["Blàñk.esp", "Blank.esm", "missing.esp"],
1✔
586
        );
1✔
587

1✔
588
        load_order.load().unwrap();
1✔
589

1✔
590
        let expected_filenames = vec![
1✔
591
            load_order.game_settings().master_file(),
1✔
592
            "Blank.esm",
1✔
593
            "Blàñk.esp",
1✔
594
            "Blank - Different.esp",
1✔
595
            "Blank - Master Dependent.esp",
1✔
596
            "Blank.esp",
1✔
597
        ];
1✔
598

1✔
599
        assert_eq!(expected_filenames, load_order.plugin_names());
1✔
600
    }
1✔
601

602
    #[test]
1✔
603
    fn load_should_add_missing_plugins() {
1✔
604
        let tmp_dir = tempdir().unwrap();
1✔
605
        let mut load_order = prepare(GameId::SkyrimSE, &tmp_dir.path());
1✔
606

1✔
607
        assert!(load_order.index_of("Blank.esm").is_none());
1✔
608
        assert!(load_order
1✔
609
            .index_of("Blank - Master Dependent.esp")
1✔
610
            .is_none());
1✔
611
        assert!(load_order.index_of("Blàñk.esp").is_none());
1✔
612

613
        load_order.load().unwrap();
1✔
614

1✔
615
        assert!(load_order.index_of("Blank.esm").is_some());
1✔
616
        assert!(load_order
1✔
617
            .index_of("Blank - Master Dependent.esp")
1✔
618
            .is_some());
1✔
619
        assert!(load_order.index_of("Blàñk.esp").is_some());
1✔
620
    }
1✔
621

622
    #[test]
1✔
623
    fn load_should_recognise_light_master_plugins() {
1✔
624
        let tmp_dir = tempdir().unwrap();
1✔
625
        let mut load_order = prepare(GameId::SkyrimSE, &tmp_dir.path());
1✔
626

1✔
627
        copy_to_test_dir("Blank.esm", "ccTest.esl", &load_order.game_settings());
1✔
628

1✔
629
        load_order.load().unwrap();
1✔
630

1✔
631
        assert!(load_order.plugin_names().contains(&"ccTest.esl"));
1✔
632
    }
1✔
633

634
    #[test]
1✔
635
    fn load_should_add_missing_implicitly_active_plugins_in_their_hardcoded_positions() {
1✔
636
        let tmp_dir = tempdir().unwrap();
1✔
637
        let mut load_order = prepare(GameId::SkyrimSE, &tmp_dir.path());
1✔
638

1✔
639
        copy_to_test_dir("Blank.esm", "Update.esm", &load_order.game_settings());
1✔
640
        load_order.load().unwrap();
1✔
641
        assert_eq!(Some(1), load_order.index_of("Update.esm"));
1✔
642
        assert!(load_order.is_active("Update.esm"));
1✔
643
    }
1✔
644

645
    #[test]
1✔
646
    fn load_should_empty_the_load_order_if_the_plugins_directory_does_not_exist() {
1✔
647
        let tmp_dir = tempdir().unwrap();
1✔
648
        let mut load_order = prepare(GameId::SkyrimSE, &tmp_dir.path());
1✔
649
        tmp_dir.close().unwrap();
1✔
650

1✔
651
        load_order.load().unwrap();
1✔
652

1✔
653
        assert!(load_order.plugins().is_empty());
1✔
654
    }
1✔
655

656
    #[test]
1✔
657
    fn load_should_load_plugin_states_from_active_plugins_file() {
1✔
658
        let tmp_dir = tempdir().unwrap();
1✔
659
        let mut load_order = prepare(GameId::SkyrimSE, &tmp_dir.path());
1✔
660

1✔
661
        write_active_plugins_file(load_order.game_settings(), &["Blàñk.esp", "Blank.esm"]);
1✔
662

1✔
663
        load_order.load().unwrap();
1✔
664
        let expected_filenames = vec!["Skyrim.esm", "Blank.esm", "Blàñk.esp"];
1✔
665

1✔
666
        assert_eq!(expected_filenames, load_order.active_plugin_names());
1✔
667
    }
1✔
668

669
    #[test]
1✔
670
    fn load_should_succeed_when_active_plugins_file_is_missing() {
1✔
671
        let tmp_dir = tempdir().unwrap();
1✔
672
        let mut load_order = prepare(GameId::SkyrimSE, &tmp_dir.path());
1✔
673

1✔
674
        assert!(load_order.load().is_ok());
1✔
675
        assert_eq!(1, load_order.active_plugin_names().len());
1✔
676
    }
1✔
677

678
    #[test]
1✔
679
    fn load_should_not_duplicate_a_plugin_that_has_a_ghosted_duplicate() {
1✔
680
        let tmp_dir = tempdir().unwrap();
1✔
681
        let mut load_order = prepare(GameId::SkyrimSE, &tmp_dir.path());
1✔
682

1✔
683
        use std::fs::copy;
1✔
684

1✔
685
        copy(
1✔
686
            load_order
1✔
687
                .game_settings()
1✔
688
                .plugins_directory()
1✔
689
                .join("Blank.esm"),
1✔
690
            load_order
1✔
691
                .game_settings()
1✔
692
                .plugins_directory()
1✔
693
                .join("Blank.esm.ghost"),
1✔
694
        )
1✔
695
        .unwrap();
1✔
696

1✔
697
        load_order.load().unwrap();
1✔
698

1✔
699
        let expected_filenames = vec![
1✔
700
            load_order.game_settings().master_file(),
1✔
701
            "Blank.esm",
1✔
702
            "Blank - Different.esp",
1✔
703
            "Blank - Master Dependent.esp",
1✔
704
            "Blank.esp",
1✔
705
            "Blàñk.esp",
1✔
706
        ];
1✔
707

1✔
708
        assert_eq!(expected_filenames, load_order.plugin_names());
1✔
709
    }
1✔
710

711
    #[test]
1✔
712
    fn load_should_not_move_light_master_esp_files_before_non_masters() {
1✔
713
        let tmp_dir = tempdir().unwrap();
1✔
714
        let mut load_order = prepare(GameId::SkyrimSE, &tmp_dir.path());
1✔
715

1✔
716
        copy_to_test_dir("Blank.esl", "Blank.esl.esp", &load_order.game_settings());
1✔
717

1✔
718
        load_order.load().unwrap();
1✔
719

1✔
720
        let expected_filenames = vec![
1✔
721
            load_order.game_settings().master_file(),
1✔
722
            "Blank.esm",
1✔
723
            "Blank - Different.esp",
1✔
724
            "Blank - Master Dependent.esp",
1✔
725
            "Blank.esl.esp",
1✔
726
            "Blank.esp",
1✔
727
            "Blàñk.esp",
1✔
728
        ];
1✔
729

1✔
730
        assert_eq!(expected_filenames, load_order.plugin_names());
1✔
731
    }
1✔
732

733
    #[test]
1✔
734
    fn load_should_add_plugins_in_additional_plugins_directory_before_those_in_main_plugins_directory(
1✔
735
    ) {
1✔
736
        let tmp_dir = tempdir().unwrap();
1✔
737
        let game_path = tmp_dir.path().join("Fallout 4/Content");
1✔
738
        create_dir_all(&game_path).unwrap();
1✔
739

1✔
740
        File::create(game_path.join("appxmanifest.xml")).unwrap();
1✔
741

1✔
742
        let mut load_order = prepare(GameId::Fallout4, &game_path);
1✔
743

1✔
744
        copy_to_test_dir("Blank.esm", "Blank.esm", &load_order.game_settings());
1✔
745

1✔
746
        let dlc_path = tmp_dir
1✔
747
            .path()
1✔
748
            .join("Fallout 4- Far Harbor (PC)/Content/Data");
1✔
749
        create_dir_all(&dlc_path).unwrap();
1✔
750
        copy_to_dir(
1✔
751
            "Blank.esm",
1✔
752
            &dlc_path,
1✔
753
            "DLCCoast.esm",
1✔
754
            &load_order.game_settings(),
1✔
755
        );
1✔
756
        copy_to_dir(
1✔
757
            "Blank.esp",
1✔
758
            &dlc_path,
1✔
759
            "Blank DLC.esp",
1✔
760
            &load_order.game_settings(),
1✔
761
        );
1✔
762

1✔
763
        load_order.load().unwrap();
1✔
764

1✔
765
        let expected_filenames = vec![
1✔
766
            "Fallout4.esm",
1✔
767
            "DLCCoast.esm",
1✔
768
            "Blank.esm",
1✔
769
            "Blank - Different.esp",
1✔
770
            "Blank - Master Dependent.esp",
1✔
771
            "Blank DLC.esp",
1✔
772
            "Blank.esp",
1✔
773
            "Blàñk.esp",
1✔
774
        ];
1✔
775

1✔
776
        assert_eq!(expected_filenames, load_order.plugin_names());
1✔
777
    }
1✔
778

779
    #[test]
1✔
780
    fn save_should_create_active_plugins_file_parent_directory_if_it_does_not_exist() {
1✔
781
        let tmp_dir = tempdir().unwrap();
1✔
782
        let mut load_order = prepare(GameId::SkyrimSE, &tmp_dir.path());
1✔
783

1✔
784
        remove_dir_all(
1✔
785
            load_order
1✔
786
                .game_settings()
1✔
787
                .active_plugins_file()
1✔
788
                .parent()
1✔
789
                .unwrap(),
1✔
790
        )
1✔
791
        .unwrap();
1✔
792

1✔
793
        load_order.save().unwrap();
1✔
794

1✔
795
        assert!(load_order
1✔
796
            .game_settings()
1✔
797
            .active_plugins_file()
1✔
798
            .parent()
1✔
799
            .unwrap()
1✔
800
            .exists());
1✔
801
    }
1✔
802

803
    #[test]
1✔
804
    fn save_should_write_active_plugins_file() {
1✔
805
        let tmp_dir = tempdir().unwrap();
1✔
806
        let mut load_order = prepare(GameId::SkyrimSE, &tmp_dir.path());
1✔
807

1✔
808
        load_order.save().unwrap();
1✔
809

1✔
810
        load_order.load().unwrap();
1✔
811
        assert_eq!(
1✔
812
            vec!["Skyrim.esm", "Blank.esp"],
1✔
813
            load_order.active_plugin_names()
1✔
814
        );
1✔
815
    }
1✔
816

817
    #[test]
1✔
818
    fn save_should_write_unghosted_plugin_names() {
1✔
819
        let tmp_dir = tempdir().unwrap();
1✔
820
        let mut load_order = prepare(GameId::SkyrimSE, &tmp_dir.path());
1✔
821

1✔
822
        copy_to_test_dir(
1✔
823
            "Blank - Different.esm",
1✔
824
            "ghosted.esm.ghost",
1✔
825
            &load_order.game_settings(),
1✔
826
        );
1✔
827
        let plugin = Plugin::new("ghosted.esm.ghost", &load_order.game_settings()).unwrap();
1✔
828
        load_order.plugins_mut().push(plugin);
1✔
829

1✔
830
        load_order.save().unwrap();
1✔
831

1✔
832
        let reader =
1✔
833
            BufReader::new(File::open(load_order.game_settings().active_plugins_file()).unwrap());
1✔
834

1✔
835
        let lines = reader
1✔
836
            .lines()
1✔
837
            .collect::<Result<Vec<String>, io::Error>>()
1✔
838
            .unwrap();
1✔
839

1✔
840
        assert_eq!(
1✔
841
            vec!["*Blank.esp", "Blank - Different.esp", "ghosted.esm"],
1✔
842
            lines
1✔
843
        );
1✔
844
    }
1✔
845

846
    #[test]
1✔
847
    fn save_should_error_if_a_plugin_filename_cannot_be_encoded_in_windows_1252() {
1✔
848
        let tmp_dir = tempdir().unwrap();
1✔
849
        let mut load_order = prepare(GameId::SkyrimSE, &tmp_dir.path());
1✔
850

1✔
851
        let filename = "Bl\u{0227}nk.esm";
1✔
852
        copy_to_test_dir(
1✔
853
            "Blank - Different.esm",
1✔
854
            filename,
1✔
855
            &load_order.game_settings(),
1✔
856
        );
1✔
857
        let plugin = Plugin::new(filename, &load_order.game_settings()).unwrap();
1✔
858
        load_order.plugins_mut().push(plugin);
1✔
859

1✔
860
        match load_order.save().unwrap_err() {
1✔
861
            Error::EncodeError(s) => assert_eq!("unrepresentable character", s),
1✔
862
            e => panic!("Expected encode error, got {:?}", e),
×
863
        };
864
    }
1✔
865

866
    #[test]
1✔
867
    fn set_load_order_should_error_if_given_an_empty_list() {
1✔
868
        let tmp_dir = tempdir().unwrap();
1✔
869
        let mut load_order = prepare(GameId::SkyrimSE, &tmp_dir.path());
1✔
870

1✔
871
        let existing_filenames = to_owned(load_order.plugin_names());
1✔
872
        let filenames = vec![];
1✔
873
        assert!(load_order.set_load_order(&filenames).is_err());
1✔
874
        assert_eq!(existing_filenames, load_order.plugin_names());
1✔
875
    }
1✔
876

877
    #[test]
1✔
878
    fn set_load_order_should_error_if_the_first_element_given_is_not_the_game_master() {
1✔
879
        let tmp_dir = tempdir().unwrap();
1✔
880
        let mut load_order = prepare(GameId::SkyrimSE, &tmp_dir.path());
1✔
881

1✔
882
        let existing_filenames = to_owned(load_order.plugin_names());
1✔
883
        let filenames = vec!["Blank.esp"];
1✔
884
        assert!(load_order.set_load_order(&filenames).is_err());
1✔
885
        assert_eq!(existing_filenames, load_order.plugin_names());
1✔
886
    }
1✔
887

888
    #[test]
1✔
889
    fn set_load_order_should_error_if_an_implicitly_active_plugin_loads_after_another_plugin() {
1✔
890
        let tmp_dir = tempdir().unwrap();
1✔
891
        let mut load_order = prepare(GameId::SkyrimSE, &tmp_dir.path());
1✔
892

1✔
893
        copy_to_test_dir("Blank.esm", "Update.esm", &load_order.game_settings());
1✔
894

1✔
895
        let filenames = vec![
1✔
896
            "Skyrim.esm",
1✔
897
            "Blank.esm",
1✔
898
            "Update.esm",
1✔
899
            "Blank.esp",
1✔
900
            "Blank - Master Dependent.esp",
1✔
901
            "Blank - Different.esp",
1✔
902
            "Blàñk.esp",
1✔
903
        ];
1✔
904

1✔
905
        match load_order.set_load_order(&filenames).unwrap_err() {
1✔
906
            Error::GameMasterMustLoadFirst => {}
1✔
907
            e => panic!("Wrong error type: {:?}", e),
×
908
        }
909
    }
1✔
910

911
    #[test]
1✔
912
    fn set_load_order_should_not_error_if_an_implicitly_active_plugin_is_missing() {
1✔
913
        let tmp_dir = tempdir().unwrap();
1✔
914
        let mut load_order = prepare(GameId::SkyrimSE, &tmp_dir.path());
1✔
915

1✔
916
        copy_to_test_dir("Blank.esm", "Dragonborn.esm", &load_order.game_settings());
1✔
917

1✔
918
        let filenames = vec![
1✔
919
            "Skyrim.esm",
1✔
920
            "Dragonborn.esm",
1✔
921
            "Blank.esm",
1✔
922
            "Blank.esp",
1✔
923
            "Blank - Master Dependent.esp",
1✔
924
            "Blank - Different.esp",
1✔
925
            "Blàñk.esp",
1✔
926
        ];
1✔
927

1✔
928
        assert!(load_order.set_load_order(&filenames).is_ok());
1✔
929
    }
1✔
930

931
    #[test]
1✔
932
    fn set_load_order_should_not_distinguish_between_ghosted_and_unghosted_filenames() {
1✔
933
        let tmp_dir = tempdir().unwrap();
1✔
934
        let mut load_order = prepare(GameId::SkyrimSE, &tmp_dir.path());
1✔
935

1✔
936
        copy_to_test_dir(
1✔
937
            "Blank - Different.esm",
1✔
938
            "ghosted.esm.ghost",
1✔
939
            &load_order.game_settings(),
1✔
940
        );
1✔
941

1✔
942
        let filenames = vec![
1✔
943
            "Skyrim.esm",
1✔
944
            "Blank.esm",
1✔
945
            "ghosted.esm",
1✔
946
            "Blank.esp",
1✔
947
            "Blank - Master Dependent.esp",
1✔
948
            "Blank - Different.esp",
1✔
949
            "Blàñk.esp",
1✔
950
        ];
1✔
951

1✔
952
        assert!(load_order.set_load_order(&filenames).is_ok());
1✔
953
    }
1✔
954

955
    #[test]
1✔
956
    fn set_load_order_should_not_insert_missing_plugins() {
1✔
957
        let tmp_dir = tempdir().unwrap();
1✔
958
        let mut load_order = prepare(GameId::SkyrimSE, &tmp_dir.path());
1✔
959

1✔
960
        let filenames = vec![
1✔
961
            "Skyrim.esm",
1✔
962
            "Blank.esm",
1✔
963
            "Blank.esp",
1✔
964
            "Blank - Master Dependent.esp",
1✔
965
            "Blank - Different.esp",
1✔
966
        ];
1✔
967
        load_order.set_load_order(&filenames).unwrap();
1✔
968

1✔
969
        assert_eq!(filenames, load_order.plugin_names());
1✔
970
    }
1✔
971

972
    #[test]
1✔
973
    fn set_load_order_should_not_lose_active_state_of_existing_plugins() {
1✔
974
        let tmp_dir = tempdir().unwrap();
1✔
975
        let mut load_order = prepare(GameId::SkyrimSE, &tmp_dir.path());
1✔
976

1✔
977
        let filenames = vec![
1✔
978
            "Skyrim.esm",
1✔
979
            "Blank.esm",
1✔
980
            "Blank.esp",
1✔
981
            "Blank - Master Dependent.esp",
1✔
982
            "Blank - Different.esp",
1✔
983
        ];
1✔
984
        load_order.set_load_order(&filenames).unwrap();
1✔
985

1✔
986
        assert!(load_order.is_active("Blank.esp"));
1✔
987
    }
1✔
988

989
    #[test]
1✔
990
    fn set_plugin_index_should_error_if_setting_the_game_master_index_to_non_zero_in_bounds() {
1✔
991
        let tmp_dir = tempdir().unwrap();
1✔
992
        let mut load_order = prepare(GameId::SkyrimSE, &tmp_dir.path());
1✔
993

1✔
994
        let existing_filenames = to_owned(load_order.plugin_names());
1✔
995
        assert!(load_order.set_plugin_index("Skyrim.esm", 1).is_err());
1✔
996
        assert_eq!(existing_filenames, load_order.plugin_names());
1✔
997
    }
1✔
998

999
    #[test]
1✔
1000
    fn set_plugin_index_should_error_if_setting_a_zero_index_for_a_non_game_master_plugin() {
1✔
1001
        let tmp_dir = tempdir().unwrap();
1✔
1002
        let mut load_order = prepare(GameId::SkyrimSE, &tmp_dir.path());
1✔
1003

1✔
1004
        let existing_filenames = to_owned(load_order.plugin_names());
1✔
1005
        assert!(load_order.set_plugin_index("Blank.esm", 0).is_err());
1✔
1006
        assert_eq!(existing_filenames, load_order.plugin_names());
1✔
1007
    }
1✔
1008

1009
    #[test]
1✔
1010
    fn set_plugin_index_should_insert_a_new_plugin() {
1✔
1011
        let tmp_dir = tempdir().unwrap();
1✔
1012
        let mut load_order = prepare(GameId::SkyrimSE, &tmp_dir.path());
1✔
1013

1✔
1014
        let num_plugins = load_order.plugins().len();
1✔
1015
        assert_eq!(1, load_order.set_plugin_index("Blank.esm", 1).unwrap());
1✔
1016
        assert_eq!(1, load_order.index_of("Blank.esm").unwrap());
1✔
1017
        assert_eq!(num_plugins + 1, load_order.plugins().len());
1✔
1018
    }
1✔
1019

1020
    #[test]
1✔
1021
    fn is_self_consistent_should_return_true() {
1✔
1022
        let tmp_dir = tempdir().unwrap();
1✔
1023
        let load_order = prepare(GameId::SkyrimSE, &tmp_dir.path());
1✔
1024

1✔
1025
        assert!(load_order.is_self_consistent().unwrap());
1✔
1026
    }
1✔
1027

1028
    #[test]
1✔
1029
    fn is_ambiguous_should_return_false_if_all_loaded_plugins_are_listed_in_active_plugins_file() {
1✔
1030
        let tmp_dir = tempdir().unwrap();
1✔
1031
        let load_order = prepare(GameId::SkyrimSE, &tmp_dir.path());
1✔
1032

1✔
1033
        let loaded_plugin_names: Vec<&str> = load_order
1✔
1034
            .plugins
1✔
1035
            .iter()
1✔
1036
            .map(|plugin| plugin.name())
3✔
1037
            .collect();
1✔
1038
        write_active_plugins_file(load_order.game_settings(), &loaded_plugin_names);
1✔
1039

1✔
1040
        assert!(!load_order.is_ambiguous().unwrap());
1✔
1041
    }
1✔
1042

1043
    #[test]
1✔
1044
    fn is_ambiguous_should_ignore_plugins_that_are_listed_in_active_plugins_file_but_not_loaded() {
1✔
1045
        let tmp_dir = tempdir().unwrap();
1✔
1046
        let load_order = prepare(GameId::SkyrimSE, &tmp_dir.path());
1✔
1047

1✔
1048
        assert!(load_order.index_of("missing.esp").is_none());
1✔
1049

1050
        let mut loaded_plugin_names: Vec<&str> = load_order
1✔
1051
            .plugins
1✔
1052
            .iter()
1✔
1053
            .map(|plugin| plugin.name())
3✔
1054
            .collect();
1✔
1055
        loaded_plugin_names.push("missing.esp");
1✔
1056

1✔
1057
        write_active_plugins_file(load_order.game_settings(), &loaded_plugin_names);
1✔
1058

1✔
1059
        assert!(!load_order.is_ambiguous().unwrap());
1✔
1060
    }
1✔
1061

1062
    #[test]
1✔
1063
    fn is_ambiguous_should_ignore_loaded_implicitly_active_plugins_not_listed_in_active_plugins_file(
1✔
1064
    ) {
1✔
1065
        let tmp_dir = tempdir().unwrap();
1✔
1066
        let mut load_order = prepare(GameId::SkyrimSE, &tmp_dir.path());
1✔
1067

1✔
1068
        let loaded_plugin_names: Vec<&str> = load_order
1✔
1069
            .plugins
1✔
1070
            .iter()
1✔
1071
            .map(|plugin| plugin.name())
3✔
1072
            .collect();
1✔
1073

1✔
1074
        write_active_plugins_file(load_order.game_settings(), &loaded_plugin_names);
1✔
1075

1✔
1076
        copy_to_test_dir("Blank.esm", "Dawnguard.esm", &load_order.game_settings());
1✔
1077
        let plugin = Plugin::new("Dawnguard.esm", &load_order.game_settings()).unwrap();
1✔
1078
        load_order.plugins_mut().push(plugin);
1✔
1079

1✔
1080
        assert!(!load_order.is_ambiguous().unwrap());
1✔
1081
    }
1✔
1082

1083
    #[test]
1✔
1084
    fn is_ambiguous_should_return_true_if_there_are_loaded_plugins_not_in_active_plugins_file() {
1✔
1085
        let tmp_dir = tempdir().unwrap();
1✔
1086
        let load_order = prepare(GameId::SkyrimSE, &tmp_dir.path());
1✔
1087

1✔
1088
        let mut loaded_plugin_names: Vec<&str> = load_order
1✔
1089
            .plugins
1✔
1090
            .iter()
1✔
1091
            .map(|plugin| plugin.name())
3✔
1092
            .collect();
1✔
1093

1✔
1094
        loaded_plugin_names.pop();
1✔
1095

1✔
1096
        write_active_plugins_file(load_order.game_settings(), &loaded_plugin_names);
1✔
1097

1✔
1098
        assert!(load_order.is_ambiguous().unwrap());
1✔
1099
    }
1✔
1100

1101
    #[test]
1✔
1102
    fn activate_should_check_normal_plugins_and_light_masters_active_limits_separately() {
1✔
1103
        let tmp_dir = tempdir().unwrap();
1✔
1104
        let mut load_order = prepare(GameId::SkyrimSE, &tmp_dir.path());
1✔
1105

1✔
1106
        let plugins = prepare_bulk_plugins(load_order.game_settings());
1✔
1107

1✔
1108
        let mut plugin_refs: Vec<&str> = plugins[..254].iter().map(AsRef::as_ref).collect();
1✔
1109
        plugin_refs.extend(plugins[261..4356].iter().map(|s| s.as_str()));
4,095✔
1110

1✔
1111
        load_order.load().unwrap();
1✔
1112
        assert!(load_order.set_active_plugins(&plugin_refs).is_ok());
1✔
1113

1114
        let i = 4356;
1✔
1115
        assert!(load_order.activate(&plugins[i]).is_ok());
1✔
1116
        assert!(load_order.is_active(&plugins[i]));
1✔
1117

1118
        let i = 254;
1✔
1119
        assert!(load_order.activate(&plugins[i]).is_ok());
1✔
1120
        assert!(load_order.is_active(&plugins[i]));
1✔
1121

1122
        let i = 256;
1✔
1123
        assert!(load_order.activate(&plugins[i]).is_err());
1✔
1124
        assert!(!load_order.is_active(&plugins[i]));
1✔
1125

1126
        let i = 4357;
1✔
1127
        assert!(load_order.activate(&plugins[i]).is_err());
1✔
1128
        assert!(!load_order.is_active(&plugins[i]));
1✔
1129
    }
1✔
1130

1131
    #[test]
1✔
1132
    fn set_active_plugins_should_count_light_masters_and_normal_plugins_separately() {
1✔
1133
        let tmp_dir = tempdir().unwrap();
1✔
1134
        let mut load_order = prepare(GameId::SkyrimSE, &tmp_dir.path());
1✔
1135

1✔
1136
        let plugins = prepare_bulk_plugins(load_order.game_settings());
1✔
1137

1✔
1138
        let mut plugin_refs: Vec<&str> = plugins[..255].iter().map(AsRef::as_ref).collect();
1✔
1139
        plugin_refs.extend(plugins[261..4357].iter().map(|s| s.as_str()));
4,096✔
1140

1✔
1141
        load_order.load().unwrap();
1✔
1142
        assert!(load_order.set_active_plugins(&plugin_refs).is_ok());
1✔
1143
        assert_eq!(4351, load_order.active_plugin_names().len());
1✔
1144
    }
1✔
1145

1146
    #[test]
1✔
1147
    fn set_active_plugins_should_error_if_given_more_than_4096_light_masters() {
1✔
1148
        let tmp_dir = tempdir().unwrap();
1✔
1149
        let mut load_order = prepare(GameId::SkyrimSE, &tmp_dir.path());
1✔
1150

1✔
1151
        let plugins = prepare_bulk_plugins(load_order.game_settings());
1✔
1152

1✔
1153
        let mut plugin_refs: Vec<&str> = plugins[..255].iter().map(AsRef::as_ref).collect();
1✔
1154
        plugin_refs.extend(plugins[261..4358].iter().map(|s| s.as_str()));
4,097✔
1155

1✔
1156
        assert!(load_order.set_active_plugins(&plugin_refs).is_err());
1✔
1157
        assert_eq!(1, load_order.active_plugin_names().len());
1✔
1158
    }
1✔
1159
}
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