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

Ortham / libloadorder / 6304723708

25 Sep 2023 08:37PM UTC coverage: 90.445% (+1.7%) from 88.77%
6304723708

push

github

Ortham
Refresh implicitly active plugins when loading current state

Now that implicitly active plugins are pulled from sources that can't be
reasonably assumed to be unchanging, refresh them as part of loading
current state in case they've changed since the last time load order
data was loaded.

Do it as the first thing so that a failure doesn't mess with existing
state.

152 of 152 new or added lines in 4 files covered. (100.0%)

6664 of 7368 relevant lines covered (90.45%)

70436.87 hits per line

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

98.15
/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> {
22✔
50
        if self.ignore_active_plugins_file() {
22✔
51
            Ok(Vec::new())
1✔
52
        } else {
53
            read_plugin_names(
21✔
54
                self.game_settings().active_plugins_file(),
21✔
55
                owning_plugin_line_mapper,
21✔
56
            )
21✔
57
        }
58
    }
22✔
59

60
    fn ignore_active_plugins_file(&self) -> bool {
33✔
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.
33✔
62
        (self.game_settings.id() == GameId::Fallout4
33✔
63
            || self.game_settings.id() == GameId::Fallout4VR)
28✔
64
            && self.game_settings.implicitly_active_plugins().len()
7✔
65
                > self.game_settings.early_loading_plugins().len()
7✔
66
    }
33✔
67
}
68

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

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

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

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

23✔
90
            let mut loaded_plugin_count = 0;
23✔
91
            for plugin_name in self.game_settings().early_loading_plugins() {
33✔
92
                if eq(plugin.name(), plugin_name) {
33✔
93
                    return Some(loaded_plugin_count);
23✔
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,633✔
101

102
        generic_insert_position(self.plugins(), plugin)
10,633✔
103
    }
10,659✔
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> {
22✔
112
        self.plugins_mut().clear();
22✔
113

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

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

120
        self.add_implicitly_active_plugins()?;
22✔
121

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

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

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

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

144
        Ok(())
5✔
145
    }
6✔
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> {
8✔
156
        let is_game_master_first = plugin_names
8✔
157
            .first()
8✔
158
            .map(|name| eq(*name, self.game_settings().master_file()))
8✔
159
            .unwrap_or(false);
8✔
160
        if !is_game_master_first {
8✔
161
            return Err(Error::GameMasterMustLoadFirst);
2✔
162
        }
6✔
163

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

188
        self.replace_plugins(plugin_names)
5✔
189
    }
8✔
190

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

1✔
202
        self.move_or_insert_plugin_with_index(plugin_name, position)
1✔
203
    }
3✔
204

205
    fn is_self_consistent(&self) -> Result<bool, Error> {
1✔
206
        Ok(true)
1✔
207
    }
1✔
208

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

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

226
        // Check if all loaded plugins aside from early loading plugins
227
        // (which don't get written to the active plugins file) are named in the
228
        // set.
229
        let all_plugins_listed = self
5✔
230
            .plugins
5✔
231
            .iter()
5✔
232
            .filter(|plugin| !self.game_settings().loads_early(plugin.name()))
15✔
233
            .all(|plugin| set.contains(&plugin.name().to_lowercase()));
9✔
234

5✔
235
        Ok(!all_plugins_listed)
5✔
236
    }
5✔
237

238
    fn activate(&mut self, plugin_name: &str) -> Result<(), Error> {
4✔
239
        activate(self, plugin_name)
4✔
240
    }
4✔
241

242
    fn deactivate(&mut self, plugin_name: &str) -> Result<(), Error> {
×
243
        deactivate(self, plugin_name)
×
244
    }
×
245

246
    fn set_active_plugins(&mut self, active_plugin_names: &[&str]) -> Result<(), Error> {
3✔
247
        set_active_plugins(self, active_plugin_names)
3✔
248
    }
3✔
249
}
250

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

261
fn owning_plugin_line_mapper(line: &str) -> Option<(String, bool)> {
10,545✔
262
    plugin_line_mapper(line).map(|(name, active)| (name.to_owned(), active))
10,545✔
263
}
10,545✔
264

265
#[cfg(test)]
266
mod tests {
267
    use super::*;
268

269
    use crate::enums::GameId;
270
    use crate::load_order::tests::*;
271
    use crate::tests::{copy_to_dir, copy_to_test_dir};
272
    use filetime::{set_file_times, FileTime};
273
    use std::fs::{create_dir_all, remove_dir_all, File};
274
    use std::io;
275
    use std::io::{BufRead, BufReader};
276
    use std::path::Path;
277
    use tempfile::tempdir;
278

279
    fn prepare(game_id: GameId, game_dir: &Path) -> AsteriskBasedLoadOrder {
58✔
280
        let (game_settings, plugins) = mock_game_files(game_id, game_dir);
58✔
281
        AsteriskBasedLoadOrder {
58✔
282
            game_settings,
58✔
283
            plugins,
58✔
284
        }
58✔
285
    }
58✔
286

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

292
        for plugin in &plugins {
15,786✔
293
            copy_to_test_dir("Blank - Different.esm", &plugin, game_settings);
15,783✔
294
        }
15,783✔
295

296
        write_active_plugins_file(game_settings, &plugins);
3✔
297

3✔
298
        plugins
3✔
299
    }
3✔
300

301
    #[test]
1✔
302
    fn ignore_active_plugins_file_should_be_true_for_fallout4_when_test_files_are_configured() {
1✔
303
        let tmp_dir = tempdir().unwrap();
1✔
304

1✔
305
        let ini_path = tmp_dir.path().join("my games/Fallout4.ini");
1✔
306
        create_parent_dirs(&ini_path).unwrap();
1✔
307
        std::fs::write(&ini_path, "[General]\nsTestFile1=Blank.esp").unwrap();
1✔
308

1✔
309
        let load_order = prepare(GameId::Fallout4, &tmp_dir.path());
1✔
310

1✔
311
        assert!(load_order.ignore_active_plugins_file());
1✔
312
    }
1✔
313

314
    #[test]
1✔
315
    fn ignore_active_plugins_file_should_be_false_for_fallout4_when_test_files_are_not_configured()
1✔
316
    {
1✔
317
        let tmp_dir = tempdir().unwrap();
1✔
318
        let load_order = prepare(GameId::Fallout4, &tmp_dir.path());
1✔
319

1✔
320
        assert!(!load_order.ignore_active_plugins_file());
1✔
321
    }
1✔
322

323
    #[test]
1✔
324
    fn ignore_active_plugins_file_should_be_true_for_fallout4vr_when_test_files_are_configured() {
1✔
325
        let tmp_dir = tempdir().unwrap();
1✔
326

1✔
327
        let ini_path = tmp_dir.path().join("my games/Fallout4VR.ini");
1✔
328
        create_parent_dirs(&ini_path).unwrap();
1✔
329
        std::fs::write(&ini_path, "[General]\nsTestFile1=Blank.esp").unwrap();
1✔
330

1✔
331
        let load_order = prepare(GameId::Fallout4VR, &tmp_dir.path());
1✔
332

1✔
333
        assert!(load_order.ignore_active_plugins_file());
1✔
334
    }
1✔
335

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

1✔
342
        assert!(!load_order.ignore_active_plugins_file());
1✔
343
    }
1✔
344

345
    #[test]
1✔
346
    fn ignore_active_plugins_file_should_be_false_for_skyrimse() {
1✔
347
        let tmp_dir = tempdir().unwrap();
1✔
348

1✔
349
        let ini_path = tmp_dir.path().join("my games/Skyrim.ini");
1✔
350
        create_parent_dirs(&ini_path).unwrap();
1✔
351
        std::fs::write(&ini_path, "[General]\nsTestFile1=a").unwrap();
1✔
352

1✔
353
        let load_order = prepare(GameId::SkyrimSE, &tmp_dir.path());
1✔
354

1✔
355
        assert!(!load_order.ignore_active_plugins_file());
1✔
356
    }
1✔
357

358
    #[test]
1✔
359
    fn ignore_active_plugins_file_should_be_false_for_skyrimvr() {
1✔
360
        let tmp_dir = tempdir().unwrap();
1✔
361

1✔
362
        let ini_path = tmp_dir.path().join("my games/SkyrimVR.ini");
1✔
363
        create_parent_dirs(&ini_path).unwrap();
1✔
364
        std::fs::write(&ini_path, "[General]\nsTestFile1=a").unwrap();
1✔
365

1✔
366
        let load_order = prepare(GameId::SkyrimVR, &tmp_dir.path());
1✔
367

1✔
368
        assert!(!load_order.ignore_active_plugins_file());
1✔
369
    }
1✔
370

371
    #[test]
1✔
372
    fn insert_position_should_return_none_for_the_game_master_if_no_plugins_are_loaded() {
1✔
373
        let tmp_dir = tempdir().unwrap();
1✔
374
        let mut load_order = prepare(GameId::SkyrimSE, &tmp_dir.path());
1✔
375

1✔
376
        load_order.plugins_mut().clear();
1✔
377

1✔
378
        let plugin = Plugin::new("Skyrim.esm", &load_order.game_settings()).unwrap();
1✔
379
        let position = load_order.insert_position(&plugin);
1✔
380

1✔
381
        assert!(position.is_none());
1✔
382
    }
1✔
383

384
    #[test]
1✔
385
    fn insert_position_should_return_the_hardcoded_index_of_an_early_loading_plugin() {
1✔
386
        let tmp_dir = tempdir().unwrap();
1✔
387
        let mut load_order = prepare(GameId::SkyrimSE, &tmp_dir.path());
1✔
388

1✔
389
        let plugin = Plugin::new("Blank.esm", &load_order.game_settings()).unwrap();
1✔
390
        load_order.plugins_mut().insert(1, plugin);
1✔
391

1✔
392
        copy_to_test_dir("Blank.esm", "HearthFires.esm", &load_order.game_settings());
1✔
393
        let plugin = Plugin::new("HearthFires.esm", &load_order.game_settings()).unwrap();
1✔
394
        let position = load_order.insert_position(&plugin);
1✔
395

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

399
    #[test]
1✔
400
    fn insert_position_should_not_treat_all_implicitly_active_plugins_as_early_loading_plugins() {
1✔
401
        let tmp_dir = tempdir().unwrap();
1✔
402

1✔
403
        let ini_path = tmp_dir.path().join("my games/Skyrim.ini");
1✔
404
        create_parent_dirs(&ini_path).unwrap();
1✔
405
        std::fs::write(&ini_path, "[General]\nsTestFile1=Blank.esm").unwrap();
1✔
406

1✔
407
        let mut load_order = prepare(GameId::SkyrimSE, &tmp_dir.path());
1✔
408

1✔
409
        copy_to_test_dir(
1✔
410
            "Blank.esm",
1✔
411
            "Blank - Different.esm",
1✔
412
            &load_order.game_settings(),
1✔
413
        );
1✔
414
        let plugin = Plugin::new("Blank - Different.esm", &load_order.game_settings()).unwrap();
1✔
415
        load_order.plugins_mut().insert(1, plugin);
1✔
416

1✔
417
        let plugin = Plugin::new("Blank.esm", &load_order.game_settings()).unwrap();
1✔
418
        let position = load_order.insert_position(&plugin);
1✔
419

1✔
420
        assert_eq!(2, position.unwrap());
1✔
421
    }
1✔
422

423
    #[test]
1✔
424
    fn insert_position_should_not_count_installed_unloaded_early_loading_plugins() {
1✔
425
        let tmp_dir = tempdir().unwrap();
1✔
426
        let load_order = prepare(GameId::SkyrimSE, &tmp_dir.path());
1✔
427

1✔
428
        copy_to_test_dir("Blank.esm", "Update.esm", &load_order.game_settings());
1✔
429
        copy_to_test_dir("Blank.esm", "HearthFires.esm", &load_order.game_settings());
1✔
430
        let plugin = Plugin::new("HearthFires.esm", &load_order.game_settings()).unwrap();
1✔
431
        let position = load_order.insert_position(&plugin);
1✔
432

1✔
433
        assert_eq!(1, position.unwrap());
1✔
434
    }
1✔
435

436
    #[test]
1✔
437
    fn insert_position_should_return_none_if_given_a_non_master_plugin() {
1✔
438
        let tmp_dir = tempdir().unwrap();
1✔
439
        let load_order = prepare(GameId::SkyrimSE, &tmp_dir.path());
1✔
440

1✔
441
        let plugin =
1✔
442
            Plugin::new("Blank - Master Dependent.esp", &load_order.game_settings()).unwrap();
1✔
443
        let position = load_order.insert_position(&plugin);
1✔
444

1✔
445
        assert_eq!(None, position);
1✔
446
    }
1✔
447

448
    #[test]
1✔
449
    fn insert_position_should_return_the_first_non_master_plugin_index_if_given_a_master_plugin() {
1✔
450
        let tmp_dir = tempdir().unwrap();
1✔
451
        let load_order = prepare(GameId::SkyrimSE, &tmp_dir.path());
1✔
452

1✔
453
        let plugin = Plugin::new("Blank.esm", &load_order.game_settings()).unwrap();
1✔
454
        let position = load_order.insert_position(&plugin);
1✔
455

1✔
456
        assert_eq!(1, position.unwrap());
1✔
457
    }
1✔
458

459
    #[test]
1✔
460
    fn insert_position_should_return_none_if_no_non_masters_are_present() {
1✔
461
        let tmp_dir = tempdir().unwrap();
1✔
462
        let mut load_order = prepare(GameId::SkyrimSE, &tmp_dir.path());
1✔
463

1✔
464
        // Remove non-master plugins from the load order.
1✔
465
        load_order.plugins_mut().retain(|p| p.is_master_file());
3✔
466

1✔
467
        let plugin = Plugin::new("Blank.esm", &load_order.game_settings()).unwrap();
1✔
468
        let position = load_order.insert_position(&plugin);
1✔
469

1✔
470
        assert_eq!(None, position);
1✔
471
    }
1✔
472

473
    #[test]
1✔
474
    fn insert_position_should_return_the_first_non_master_index_if_given_a_light_master() {
1✔
475
        let tmp_dir = tempdir().unwrap();
1✔
476
        let mut load_order = prepare(GameId::SkyrimSE, &tmp_dir.path());
1✔
477

1✔
478
        copy_to_test_dir("Blank.esm", "Blank.esl", load_order.game_settings());
1✔
479
        let plugin = Plugin::new("Blank.esl", &load_order.game_settings()).unwrap();
1✔
480

1✔
481
        load_order.plugins_mut().insert(1, plugin);
1✔
482

1✔
483
        let position = load_order.insert_position(&load_order.plugins()[1]);
1✔
484

1✔
485
        assert_eq!(2, position.unwrap());
1✔
486

487
        copy_to_test_dir(
1✔
488
            "Blank.esp",
1✔
489
            "Blank - Different.esl",
1✔
490
            load_order.game_settings(),
1✔
491
        );
1✔
492
        let plugin = Plugin::new("Blank - Different.esl", &load_order.game_settings()).unwrap();
1✔
493

1✔
494
        let position = load_order.insert_position(&plugin);
1✔
495

1✔
496
        assert_eq!(2, position.unwrap());
1✔
497
    }
1✔
498

499
    #[test]
1✔
500
    fn load_should_reload_existing_plugins() {
1✔
501
        let tmp_dir = tempdir().unwrap();
1✔
502
        let mut load_order = prepare(GameId::SkyrimSE, &tmp_dir.path());
1✔
503

1✔
504
        assert!(!load_order.plugins()[1].is_master_file());
1✔
505
        copy_to_test_dir("Blank.esm", "Blank.esp", &load_order.game_settings());
1✔
506
        let plugin_path = load_order
1✔
507
            .game_settings()
1✔
508
            .plugins_directory()
1✔
509
            .join("Blank.esp");
1✔
510
        set_file_times(&plugin_path, FileTime::zero(), FileTime::zero()).unwrap();
1✔
511

1✔
512
        load_order.load().unwrap();
1✔
513

1✔
514
        assert!(load_order.plugins()[1].is_master_file());
1✔
515
    }
1✔
516

517
    #[test]
1✔
518
    fn load_should_remove_plugins_that_fail_to_load() {
1✔
519
        let tmp_dir = tempdir().unwrap();
1✔
520
        let mut load_order = prepare(GameId::SkyrimSE, &tmp_dir.path());
1✔
521

1✔
522
        assert!(load_order.index_of("Blank.esp").is_some());
1✔
523
        assert!(load_order.index_of("Blank - Different.esp").is_some());
1✔
524

525
        let plugin_path = load_order
1✔
526
            .game_settings()
1✔
527
            .plugins_directory()
1✔
528
            .join("Blank.esp");
1✔
529
        File::create(&plugin_path).unwrap();
1✔
530
        set_file_times(&plugin_path, FileTime::zero(), FileTime::zero()).unwrap();
1✔
531

1✔
532
        let plugin_path = load_order
1✔
533
            .game_settings()
1✔
534
            .plugins_directory()
1✔
535
            .join("Blank - Different.esp");
1✔
536
        File::create(&plugin_path).unwrap();
1✔
537
        set_file_times(&plugin_path, FileTime::zero(), FileTime::zero()).unwrap();
1✔
538

1✔
539
        load_order.load().unwrap();
1✔
540
        assert!(load_order.index_of("Blank.esp").is_none());
1✔
541
        assert!(load_order.index_of("Blank - Different.esp").is_none());
1✔
542
    }
1✔
543

544
    #[test]
1✔
545
    fn load_should_get_load_order_from_active_plugins_file() {
1✔
546
        let tmp_dir = tempdir().unwrap();
1✔
547
        let mut load_order = prepare(GameId::SkyrimSE, &tmp_dir.path());
1✔
548

1✔
549
        write_active_plugins_file(
1✔
550
            load_order.game_settings(),
1✔
551
            &["Blank.esp", "Blank - Master Dependent.esp"],
1✔
552
        );
1✔
553

1✔
554
        load_order.load().unwrap();
1✔
555

1✔
556
        let expected_filenames = vec![
1✔
557
            load_order.game_settings().master_file(),
1✔
558
            "Blank.esm",
1✔
559
            "Blank.esp",
1✔
560
            "Blank - Master Dependent.esp",
1✔
561
            "Blank - Different.esp",
1✔
562
            "Blàñk.esp",
1✔
563
        ];
1✔
564

1✔
565
        assert_eq!(expected_filenames, load_order.plugin_names());
1✔
566
    }
1✔
567

568
    #[test]
1✔
569
    fn load_should_hoist_non_masters_that_masters_depend_on_to_load_before_their_dependents() {
1✔
570
        let tmp_dir = tempdir().unwrap();
1✔
571
        let mut load_order = prepare(GameId::SkyrimSE, &tmp_dir.path());
1✔
572

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

1✔
577
        let plugins_dir = &load_order.game_settings().plugins_directory();
1✔
578
        copy_to_test_dir(
1✔
579
            "Blank - Plugin Dependent.esp",
1✔
580
            "Blank - Plugin Dependent.esp",
1✔
581
            load_order.game_settings(),
1✔
582
        );
1✔
583
        set_master_flag(&plugins_dir.join("Blank - Plugin Dependent.esp"), true).unwrap();
1✔
584

1✔
585
        let expected_filenames = vec![
1✔
586
            "Blank - Master Dependent.esp",
1✔
587
            "Blank - Different.esp",
1✔
588
            "Blàñk.esp",
1✔
589
            "Blank.esp",
1✔
590
            "Skyrim.esm",
1✔
591
            "Blank - Plugin Dependent.esp",
1✔
592
            "Blank.esm",
1✔
593
        ];
1✔
594
        write_active_plugins_file(load_order.game_settings(), &expected_filenames);
1✔
595

1✔
596
        load_order.load().unwrap();
1✔
597

1✔
598
        let expected_filenames = vec![
1✔
599
            "Skyrim.esm",
1✔
600
            "Blank.esp",
1✔
601
            "Blank - Plugin Dependent.esp",
1✔
602
            "Blank.esm",
1✔
603
            "Blank - Master Dependent.esp",
1✔
604
            "Blank - Different.esp",
1✔
605
            "Blàñk.esp",
1✔
606
        ];
1✔
607

1✔
608
        assert_eq!(expected_filenames, load_order.plugin_names());
1✔
609
    }
1✔
610

611
    #[test]
1✔
612
    fn load_should_decode_active_plugins_file_from_windows_1252() {
1✔
613
        let tmp_dir = tempdir().unwrap();
1✔
614
        let mut load_order = prepare(GameId::SkyrimSE, &tmp_dir.path());
1✔
615

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

1✔
618
        load_order.load().unwrap();
1✔
619

1✔
620
        let expected_filenames = vec![
1✔
621
            load_order.game_settings().master_file(),
1✔
622
            "Blank.esm",
1✔
623
            "Blàñk.esp",
1✔
624
            "Blank - Different.esp",
1✔
625
            "Blank - Master Dependent.esp",
1✔
626
            "Blank.esp",
1✔
627
        ];
1✔
628

1✔
629
        assert_eq!(expected_filenames, load_order.plugin_names());
1✔
630
    }
1✔
631

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

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

1✔
639
        load_order.load().unwrap();
1✔
640

1✔
641
        let expected_filenames = vec![
1✔
642
            load_order.game_settings().master_file(),
1✔
643
            "Blank.esm",
1✔
644
            "Blàñk.esp",
1✔
645
            "Blank - Different.esp",
1✔
646
            "Blank - Master Dependent.esp",
1✔
647
            "Blank.esp",
1✔
648
        ];
1✔
649

1✔
650
        assert_eq!(expected_filenames, load_order.plugin_names());
1✔
651
    }
1✔
652

653
    #[test]
1✔
654
    fn load_should_ignore_active_plugins_file_lines_starting_with_a_hash() {
1✔
655
        let tmp_dir = tempdir().unwrap();
1✔
656
        let mut load_order = prepare(GameId::SkyrimSE, &tmp_dir.path());
1✔
657

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

1✔
663
        load_order.load().unwrap();
1✔
664

1✔
665
        let expected_filenames = vec![
1✔
666
            load_order.game_settings().master_file(),
1✔
667
            "Blank.esm",
1✔
668
            "Blàñk.esp",
1✔
669
            "Blank - Different.esp",
1✔
670
            "Blank - Master Dependent.esp",
1✔
671
            "Blank.esp",
1✔
672
        ];
1✔
673

1✔
674
        assert_eq!(expected_filenames, load_order.plugin_names());
1✔
675
    }
1✔
676

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

1✔
682
        write_active_plugins_file(
1✔
683
            load_order.game_settings(),
1✔
684
            &["Blàñk.esp", "Blank.esm", "missing.esp"],
1✔
685
        );
1✔
686

1✔
687
        load_order.load().unwrap();
1✔
688

1✔
689
        let expected_filenames = vec![
1✔
690
            load_order.game_settings().master_file(),
1✔
691
            "Blank.esm",
1✔
692
            "Blàñk.esp",
1✔
693
            "Blank - Different.esp",
1✔
694
            "Blank - Master Dependent.esp",
1✔
695
            "Blank.esp",
1✔
696
        ];
1✔
697

1✔
698
        assert_eq!(expected_filenames, load_order.plugin_names());
1✔
699
    }
1✔
700

701
    #[test]
1✔
702
    fn load_should_add_missing_plugins() {
1✔
703
        let tmp_dir = tempdir().unwrap();
1✔
704
        let mut load_order = prepare(GameId::SkyrimSE, &tmp_dir.path());
1✔
705

1✔
706
        assert!(load_order.index_of("Blank.esm").is_none());
1✔
707
        assert!(load_order
1✔
708
            .index_of("Blank - Master Dependent.esp")
1✔
709
            .is_none());
1✔
710
        assert!(load_order.index_of("Blàñk.esp").is_none());
1✔
711

712
        load_order.load().unwrap();
1✔
713

1✔
714
        assert!(load_order.index_of("Blank.esm").is_some());
1✔
715
        assert!(load_order
1✔
716
            .index_of("Blank - Master Dependent.esp")
1✔
717
            .is_some());
1✔
718
        assert!(load_order.index_of("Blàñk.esp").is_some());
1✔
719
    }
1✔
720

721
    #[test]
1✔
722
    fn load_should_recognise_light_master_plugins() {
1✔
723
        let tmp_dir = tempdir().unwrap();
1✔
724
        let mut load_order = prepare(GameId::SkyrimSE, &tmp_dir.path());
1✔
725

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

1✔
728
        load_order.load().unwrap();
1✔
729

1✔
730
        assert!(load_order.plugin_names().contains(&"ccTest.esl"));
1✔
731
    }
1✔
732

733
    #[test]
1✔
734
    fn load_should_add_missing_early_loading_plugins_in_their_hardcoded_positions() {
1✔
735
        let tmp_dir = tempdir().unwrap();
1✔
736
        let mut load_order = prepare(GameId::SkyrimSE, &tmp_dir.path());
1✔
737

1✔
738
        copy_to_test_dir("Blank.esm", "Update.esm", &load_order.game_settings());
1✔
739
        load_order.load().unwrap();
1✔
740
        assert_eq!(Some(1), load_order.index_of("Update.esm"));
1✔
741
        assert!(load_order.is_active("Update.esm"));
1✔
742
    }
1✔
743

744
    #[test]
1✔
745
    fn load_should_empty_the_load_order_if_the_plugins_directory_does_not_exist() {
1✔
746
        let tmp_dir = tempdir().unwrap();
1✔
747
        let mut load_order = prepare(GameId::SkyrimSE, &tmp_dir.path());
1✔
748
        tmp_dir.close().unwrap();
1✔
749

1✔
750
        load_order.load().unwrap();
1✔
751

1✔
752
        assert!(load_order.plugins().is_empty());
1✔
753
    }
1✔
754

755
    #[test]
1✔
756
    fn load_should_load_plugin_states_from_active_plugins_file() {
1✔
757
        let tmp_dir = tempdir().unwrap();
1✔
758
        let mut load_order = prepare(GameId::SkyrimSE, &tmp_dir.path());
1✔
759

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

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

1✔
765
        assert_eq!(expected_filenames, load_order.active_plugin_names());
1✔
766
    }
1✔
767

768
    #[test]
1✔
769
    fn load_should_succeed_when_active_plugins_file_is_missing() {
1✔
770
        let tmp_dir = tempdir().unwrap();
1✔
771
        let mut load_order = prepare(GameId::SkyrimSE, &tmp_dir.path());
1✔
772

1✔
773
        assert!(load_order.load().is_ok());
1✔
774
        assert_eq!(1, load_order.active_plugin_names().len());
1✔
775
    }
1✔
776

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

1✔
782
        use std::fs::copy;
1✔
783

1✔
784
        copy(
1✔
785
            load_order
1✔
786
                .game_settings()
1✔
787
                .plugins_directory()
1✔
788
                .join("Blank.esm"),
1✔
789
            load_order
1✔
790
                .game_settings()
1✔
791
                .plugins_directory()
1✔
792
                .join("Blank.esm.ghost"),
1✔
793
        )
1✔
794
        .unwrap();
1✔
795

1✔
796
        load_order.load().unwrap();
1✔
797

1✔
798
        let expected_filenames = vec![
1✔
799
            load_order.game_settings().master_file(),
1✔
800
            "Blank.esm",
1✔
801
            "Blank - Different.esp",
1✔
802
            "Blank - Master Dependent.esp",
1✔
803
            "Blank.esp",
1✔
804
            "Blàñk.esp",
1✔
805
        ];
1✔
806

1✔
807
        assert_eq!(expected_filenames, load_order.plugin_names());
1✔
808
    }
1✔
809

810
    #[test]
1✔
811
    fn load_should_not_move_light_master_esp_files_before_non_masters() {
1✔
812
        let tmp_dir = tempdir().unwrap();
1✔
813
        let mut load_order = prepare(GameId::SkyrimSE, &tmp_dir.path());
1✔
814

1✔
815
        copy_to_test_dir("Blank.esl", "Blank.esl.esp", &load_order.game_settings());
1✔
816

1✔
817
        load_order.load().unwrap();
1✔
818

1✔
819
        let expected_filenames = vec![
1✔
820
            load_order.game_settings().master_file(),
1✔
821
            "Blank.esm",
1✔
822
            "Blank - Different.esp",
1✔
823
            "Blank - Master Dependent.esp",
1✔
824
            "Blank.esl.esp",
1✔
825
            "Blank.esp",
1✔
826
            "Blàñk.esp",
1✔
827
        ];
1✔
828

1✔
829
        assert_eq!(expected_filenames, load_order.plugin_names());
1✔
830
    }
1✔
831

832
    #[test]
1✔
833
    fn load_should_add_plugins_in_additional_plugins_directory_before_those_in_main_plugins_directory(
1✔
834
    ) {
1✔
835
        let tmp_dir = tempdir().unwrap();
1✔
836
        let game_path = tmp_dir.path().join("Fallout 4/Content");
1✔
837
        create_dir_all(&game_path).unwrap();
1✔
838

1✔
839
        File::create(game_path.join("appxmanifest.xml")).unwrap();
1✔
840

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

1✔
843
        copy_to_test_dir("Blank.esm", "Blank.esm", &load_order.game_settings());
1✔
844

1✔
845
        let dlc_path = tmp_dir
1✔
846
            .path()
1✔
847
            .join("Fallout 4- Far Harbor (PC)/Content/Data");
1✔
848
        create_dir_all(&dlc_path).unwrap();
1✔
849
        copy_to_dir("Blank.esm", &dlc_path, "DLCCoast.esm", GameId::Fallout4);
1✔
850
        copy_to_dir("Blank.esp", &dlc_path, "Blank DLC.esp", GameId::Fallout4);
1✔
851

1✔
852
        load_order.load().unwrap();
1✔
853

1✔
854
        let expected_filenames = vec![
1✔
855
            "Fallout4.esm",
1✔
856
            "DLCCoast.esm",
1✔
857
            "Blank.esm",
1✔
858
            "Blank - Different.esp",
1✔
859
            "Blank - Master Dependent.esp",
1✔
860
            "Blank DLC.esp",
1✔
861
            "Blank.esp",
1✔
862
            "Blàñk.esp",
1✔
863
        ];
1✔
864

1✔
865
        assert_eq!(expected_filenames, load_order.plugin_names());
1✔
866
    }
1✔
867

868
    #[test]
1✔
869
    fn load_should_ignore_active_plugins_file_for_fallout4_when_test_files_are_configured() {
1✔
870
        let tmp_dir = tempdir().unwrap();
1✔
871

1✔
872
        let ini_path = tmp_dir.path().join("my games/Fallout4.ini");
1✔
873
        create_parent_dirs(&ini_path).unwrap();
1✔
874
        std::fs::write(&ini_path, "[General]\nsTestFile1=Blank.esp").unwrap();
1✔
875

1✔
876
        let mut load_order = prepare(GameId::Fallout4, &tmp_dir.path());
1✔
877

1✔
878
        write_active_plugins_file(
1✔
879
            load_order.game_settings(),
1✔
880
            &["Blank.esp", "Blank - Master Dependent.esp"],
1✔
881
        );
1✔
882

1✔
883
        load_order.load().unwrap();
1✔
884

1✔
885
        assert_eq!(
1✔
886
            vec!["Fallout4.esm", "Blank.esp"],
1✔
887
            load_order.active_plugin_names()
1✔
888
        );
1✔
889
    }
1✔
890

891
    #[test]
1✔
892
    fn save_should_create_active_plugins_file_parent_directory_if_it_does_not_exist() {
1✔
893
        let tmp_dir = tempdir().unwrap();
1✔
894
        let mut load_order = prepare(GameId::SkyrimSE, &tmp_dir.path());
1✔
895

1✔
896
        remove_dir_all(
1✔
897
            load_order
1✔
898
                .game_settings()
1✔
899
                .active_plugins_file()
1✔
900
                .parent()
1✔
901
                .unwrap(),
1✔
902
        )
1✔
903
        .unwrap();
1✔
904

1✔
905
        load_order.save().unwrap();
1✔
906

1✔
907
        assert!(load_order
1✔
908
            .game_settings()
1✔
909
            .active_plugins_file()
1✔
910
            .parent()
1✔
911
            .unwrap()
1✔
912
            .exists());
1✔
913
    }
1✔
914

915
    #[test]
1✔
916
    fn save_should_write_active_plugins_file() {
1✔
917
        let tmp_dir = tempdir().unwrap();
1✔
918
        let mut load_order = prepare(GameId::SkyrimSE, &tmp_dir.path());
1✔
919

1✔
920
        load_order.save().unwrap();
1✔
921

1✔
922
        load_order.load().unwrap();
1✔
923
        assert_eq!(
1✔
924
            vec!["Skyrim.esm", "Blank.esp"],
1✔
925
            load_order.active_plugin_names()
1✔
926
        );
1✔
927
    }
1✔
928

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

1✔
934
        copy_to_test_dir(
1✔
935
            "Blank - Different.esm",
1✔
936
            "ghosted.esm.ghost",
1✔
937
            &load_order.game_settings(),
1✔
938
        );
1✔
939
        let plugin = Plugin::new("ghosted.esm.ghost", &load_order.game_settings()).unwrap();
1✔
940
        load_order.plugins_mut().push(plugin);
1✔
941

1✔
942
        load_order.save().unwrap();
1✔
943

1✔
944
        let reader =
1✔
945
            BufReader::new(File::open(load_order.game_settings().active_plugins_file()).unwrap());
1✔
946

1✔
947
        let lines = reader
1✔
948
            .lines()
1✔
949
            .collect::<Result<Vec<String>, io::Error>>()
1✔
950
            .unwrap();
1✔
951

1✔
952
        assert_eq!(
1✔
953
            vec!["*Blank.esp", "Blank - Different.esp", "ghosted.esm"],
1✔
954
            lines
1✔
955
        );
1✔
956
    }
1✔
957

958
    #[test]
1✔
959
    fn save_should_error_if_a_plugin_filename_cannot_be_encoded_in_windows_1252() {
1✔
960
        let tmp_dir = tempdir().unwrap();
1✔
961
        let mut load_order = prepare(GameId::SkyrimSE, &tmp_dir.path());
1✔
962

1✔
963
        let filename = "Bl\u{0227}nk.esm";
1✔
964
        copy_to_test_dir(
1✔
965
            "Blank - Different.esm",
1✔
966
            filename,
1✔
967
            &load_order.game_settings(),
1✔
968
        );
1✔
969
        let plugin = Plugin::new(filename, &load_order.game_settings()).unwrap();
1✔
970
        load_order.plugins_mut().push(plugin);
1✔
971

1✔
972
        match load_order.save().unwrap_err() {
1✔
973
            Error::EncodeError(s) => assert_eq!("unrepresentable character", s),
1✔
974
            e => panic!("Expected encode error, got {:?}", e),
×
975
        };
976
    }
1✔
977

978
    #[test]
1✔
979
    fn save_should_omit_early_loading_plugins_from_active_plugins_file() {
1✔
980
        let tmp_dir = tempdir().unwrap();
1✔
981
        let mut load_order = prepare(GameId::SkyrimSE, &tmp_dir.path());
1✔
982

1✔
983
        copy_to_test_dir("Blank.esm", "HearthFires.esm", &load_order.game_settings());
1✔
984
        let plugin = Plugin::new("HearthFires.esm", &load_order.game_settings()).unwrap();
1✔
985
        load_order.plugins_mut().push(plugin);
1✔
986

1✔
987
        load_order.save().unwrap();
1✔
988

1✔
989
        let reader =
1✔
990
            BufReader::new(File::open(load_order.game_settings().active_plugins_file()).unwrap());
1✔
991

1✔
992
        let lines = reader
1✔
993
            .lines()
1✔
994
            .collect::<Result<Vec<String>, io::Error>>()
1✔
995
            .unwrap();
1✔
996

1✔
997
        assert_eq!(vec!["*Blank.esp", "Blank - Different.esp"], lines);
1✔
998
    }
1✔
999

1000
    #[test]
1✔
1001
    fn save_should_not_omit_implicitly_active_plugins_that_do_not_load_early() {
1✔
1002
        let tmp_dir = tempdir().unwrap();
1✔
1003

1✔
1004
        let ini_path = tmp_dir.path().join("my games/Skyrim.ini");
1✔
1005
        create_parent_dirs(&ini_path).unwrap();
1✔
1006
        std::fs::write(&ini_path, "[General]\nsTestFile1=Blank - Different.esp").unwrap();
1✔
1007

1✔
1008
        let mut load_order = prepare(GameId::SkyrimSE, &tmp_dir.path());
1✔
1009

1✔
1010
        load_order.load().unwrap();
1✔
1011

1✔
1012
        load_order.save().unwrap();
1✔
1013

1✔
1014
        let content = std::fs::read(load_order.game_settings().active_plugins_file()).unwrap();
1✔
1015
        let content = encoding_rs::WINDOWS_1252.decode(&content).0;
1✔
1016

1✔
1017
        let lines = content.lines().collect::<Vec<&str>>();
1✔
1018

1✔
1019
        assert_eq!(
1✔
1020
            vec![
1✔
1021
                "Blank.esm",
1✔
1022
                "*Blank - Different.esp",
1✔
1023
                "Blank - Master Dependent.esp",
1✔
1024
                "Blank.esp",
1✔
1025
                "Blàñk.esp"
1✔
1026
            ],
1✔
1027
            lines
1✔
1028
        );
1✔
1029
    }
1✔
1030

1031
    #[test]
1✔
1032
    fn set_load_order_should_error_if_given_an_empty_list() {
1✔
1033
        let tmp_dir = tempdir().unwrap();
1✔
1034
        let mut load_order = prepare(GameId::SkyrimSE, &tmp_dir.path());
1✔
1035

1✔
1036
        let existing_filenames = to_owned(load_order.plugin_names());
1✔
1037
        let filenames = vec![];
1✔
1038
        assert!(load_order.set_load_order(&filenames).is_err());
1✔
1039
        assert_eq!(existing_filenames, load_order.plugin_names());
1✔
1040
    }
1✔
1041

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

1✔
1047
        let existing_filenames = to_owned(load_order.plugin_names());
1✔
1048
        let filenames = vec!["Blank.esp"];
1✔
1049
        assert!(load_order.set_load_order(&filenames).is_err());
1✔
1050
        assert_eq!(existing_filenames, load_order.plugin_names());
1✔
1051
    }
1✔
1052

1053
    #[test]
1✔
1054
    fn set_load_order_should_error_if_an_early_loading_plugin_loads_after_another_plugin() {
1✔
1055
        let tmp_dir = tempdir().unwrap();
1✔
1056
        let mut load_order = prepare(GameId::SkyrimSE, &tmp_dir.path());
1✔
1057

1✔
1058
        copy_to_test_dir("Blank.esm", "Update.esm", &load_order.game_settings());
1✔
1059

1✔
1060
        let filenames = vec![
1✔
1061
            "Skyrim.esm",
1✔
1062
            "Blank.esm",
1✔
1063
            "Update.esm",
1✔
1064
            "Blank.esp",
1✔
1065
            "Blank - Master Dependent.esp",
1✔
1066
            "Blank - Different.esp",
1✔
1067
            "Blàñk.esp",
1✔
1068
        ];
1✔
1069

1✔
1070
        match load_order.set_load_order(&filenames).unwrap_err() {
1✔
1071
            Error::InvalidEarlyLoadingPluginPosition {
1072
                name,
1✔
1073
                pos,
1✔
1074
                expected_pos,
1✔
1075
            } => {
1✔
1076
                assert_eq!("Update.esm", name);
1✔
1077
                assert_eq!(2, pos);
1✔
1078
                assert_eq!(1, expected_pos);
1✔
1079
            }
1080
            e => panic!("Wrong error type: {:?}", e),
×
1081
        }
1082
    }
1✔
1083

1084
    #[test]
1✔
1085
    fn set_load_order_should_not_error_if_a_non_early_loading_implicitly_active_plugin_loads_after_another_plugin(
1✔
1086
    ) {
1✔
1087
        let tmp_dir = tempdir().unwrap();
1✔
1088

1✔
1089
        let ini_path = tmp_dir.path().join("my games/Skyrim.ini");
1✔
1090
        create_parent_dirs(&ini_path).unwrap();
1✔
1091
        std::fs::write(&ini_path, "[General]\nsTestFile1=Blank - Different.esp").unwrap();
1✔
1092

1✔
1093
        let mut load_order = prepare(GameId::SkyrimSE, &tmp_dir.path());
1✔
1094

1✔
1095
        let filenames = vec![
1✔
1096
            "Skyrim.esm",
1✔
1097
            "Blank.esm",
1✔
1098
            "Blank.esp",
1✔
1099
            "Blank - Master Dependent.esp",
1✔
1100
            "Blank - Different.esp",
1✔
1101
            "Blàñk.esp",
1✔
1102
        ];
1✔
1103

1✔
1104
        assert!(load_order.set_load_order(&filenames).is_ok());
1✔
1105
    }
1✔
1106

1107
    #[test]
1✔
1108
    fn set_load_order_should_not_error_if_an_early_loading_plugin_is_missing() {
1✔
1109
        let tmp_dir = tempdir().unwrap();
1✔
1110
        let mut load_order = prepare(GameId::SkyrimSE, &tmp_dir.path());
1✔
1111

1✔
1112
        copy_to_test_dir("Blank.esm", "Dragonborn.esm", &load_order.game_settings());
1✔
1113

1✔
1114
        let filenames = vec![
1✔
1115
            "Skyrim.esm",
1✔
1116
            "Dragonborn.esm",
1✔
1117
            "Blank.esm",
1✔
1118
            "Blank.esp",
1✔
1119
            "Blank - Master Dependent.esp",
1✔
1120
            "Blank - Different.esp",
1✔
1121
            "Blàñk.esp",
1✔
1122
        ];
1✔
1123

1✔
1124
        assert!(load_order.set_load_order(&filenames).is_ok());
1✔
1125
    }
1✔
1126

1127
    #[test]
1✔
1128
    fn set_load_order_should_not_distinguish_between_ghosted_and_unghosted_filenames() {
1✔
1129
        let tmp_dir = tempdir().unwrap();
1✔
1130
        let mut load_order = prepare(GameId::SkyrimSE, &tmp_dir.path());
1✔
1131

1✔
1132
        copy_to_test_dir(
1✔
1133
            "Blank - Different.esm",
1✔
1134
            "ghosted.esm.ghost",
1✔
1135
            &load_order.game_settings(),
1✔
1136
        );
1✔
1137

1✔
1138
        let filenames = vec![
1✔
1139
            "Skyrim.esm",
1✔
1140
            "Blank.esm",
1✔
1141
            "ghosted.esm",
1✔
1142
            "Blank.esp",
1✔
1143
            "Blank - Master Dependent.esp",
1✔
1144
            "Blank - Different.esp",
1✔
1145
            "Blàñk.esp",
1✔
1146
        ];
1✔
1147

1✔
1148
        assert!(load_order.set_load_order(&filenames).is_ok());
1✔
1149
    }
1✔
1150

1151
    #[test]
1✔
1152
    fn set_load_order_should_not_insert_missing_plugins() {
1✔
1153
        let tmp_dir = tempdir().unwrap();
1✔
1154
        let mut load_order = prepare(GameId::SkyrimSE, &tmp_dir.path());
1✔
1155

1✔
1156
        let filenames = vec![
1✔
1157
            "Skyrim.esm",
1✔
1158
            "Blank.esm",
1✔
1159
            "Blank.esp",
1✔
1160
            "Blank - Master Dependent.esp",
1✔
1161
            "Blank - Different.esp",
1✔
1162
        ];
1✔
1163
        load_order.set_load_order(&filenames).unwrap();
1✔
1164

1✔
1165
        assert_eq!(filenames, load_order.plugin_names());
1✔
1166
    }
1✔
1167

1168
    #[test]
1✔
1169
    fn set_load_order_should_not_lose_active_state_of_existing_plugins() {
1✔
1170
        let tmp_dir = tempdir().unwrap();
1✔
1171
        let mut load_order = prepare(GameId::SkyrimSE, &tmp_dir.path());
1✔
1172

1✔
1173
        let filenames = vec![
1✔
1174
            "Skyrim.esm",
1✔
1175
            "Blank.esm",
1✔
1176
            "Blank.esp",
1✔
1177
            "Blank - Master Dependent.esp",
1✔
1178
            "Blank - Different.esp",
1✔
1179
        ];
1✔
1180
        load_order.set_load_order(&filenames).unwrap();
1✔
1181

1✔
1182
        assert!(load_order.is_active("Blank.esp"));
1✔
1183
    }
1✔
1184

1185
    #[test]
1✔
1186
    fn set_plugin_index_should_error_if_setting_the_game_master_index_to_non_zero_in_bounds() {
1✔
1187
        let tmp_dir = tempdir().unwrap();
1✔
1188
        let mut load_order = prepare(GameId::SkyrimSE, &tmp_dir.path());
1✔
1189

1✔
1190
        let existing_filenames = to_owned(load_order.plugin_names());
1✔
1191
        assert!(load_order.set_plugin_index("Skyrim.esm", 1).is_err());
1✔
1192
        assert_eq!(existing_filenames, load_order.plugin_names());
1✔
1193
    }
1✔
1194

1195
    #[test]
1✔
1196
    fn set_plugin_index_should_error_if_setting_a_zero_index_for_a_non_game_master_plugin() {
1✔
1197
        let tmp_dir = tempdir().unwrap();
1✔
1198
        let mut load_order = prepare(GameId::SkyrimSE, &tmp_dir.path());
1✔
1199

1✔
1200
        let existing_filenames = to_owned(load_order.plugin_names());
1✔
1201
        assert!(load_order.set_plugin_index("Blank.esm", 0).is_err());
1✔
1202
        assert_eq!(existing_filenames, load_order.plugin_names());
1✔
1203
    }
1✔
1204

1205
    #[test]
1✔
1206
    fn set_plugin_index_should_insert_a_new_plugin() {
1✔
1207
        let tmp_dir = tempdir().unwrap();
1✔
1208
        let mut load_order = prepare(GameId::SkyrimSE, &tmp_dir.path());
1✔
1209

1✔
1210
        let num_plugins = load_order.plugins().len();
1✔
1211
        assert_eq!(1, load_order.set_plugin_index("Blank.esm", 1).unwrap());
1✔
1212
        assert_eq!(1, load_order.index_of("Blank.esm").unwrap());
1✔
1213
        assert_eq!(num_plugins + 1, load_order.plugins().len());
1✔
1214
    }
1✔
1215

1216
    #[test]
1✔
1217
    fn is_self_consistent_should_return_true() {
1✔
1218
        let tmp_dir = tempdir().unwrap();
1✔
1219
        let load_order = prepare(GameId::SkyrimSE, &tmp_dir.path());
1✔
1220

1✔
1221
        assert!(load_order.is_self_consistent().unwrap());
1✔
1222
    }
1✔
1223

1224
    #[test]
1✔
1225
    fn is_ambiguous_should_return_false_if_all_loaded_plugins_are_listed_in_active_plugins_file() {
1✔
1226
        let tmp_dir = tempdir().unwrap();
1✔
1227
        let load_order = prepare(GameId::SkyrimSE, &tmp_dir.path());
1✔
1228

1✔
1229
        let loaded_plugin_names: Vec<&str> = load_order
1✔
1230
            .plugins
1✔
1231
            .iter()
1✔
1232
            .map(|plugin| plugin.name())
3✔
1233
            .collect();
1✔
1234
        write_active_plugins_file(load_order.game_settings(), &loaded_plugin_names);
1✔
1235

1✔
1236
        assert!(!load_order.is_ambiguous().unwrap());
1✔
1237
    }
1✔
1238

1239
    #[test]
1✔
1240
    fn is_ambiguous_should_ignore_plugins_that_are_listed_in_active_plugins_file_but_not_loaded() {
1✔
1241
        let tmp_dir = tempdir().unwrap();
1✔
1242
        let load_order = prepare(GameId::SkyrimSE, &tmp_dir.path());
1✔
1243

1✔
1244
        assert!(load_order.index_of("missing.esp").is_none());
1✔
1245

1246
        let mut loaded_plugin_names: Vec<&str> = load_order
1✔
1247
            .plugins
1✔
1248
            .iter()
1✔
1249
            .map(|plugin| plugin.name())
3✔
1250
            .collect();
1✔
1251
        loaded_plugin_names.push("missing.esp");
1✔
1252

1✔
1253
        write_active_plugins_file(load_order.game_settings(), &loaded_plugin_names);
1✔
1254

1✔
1255
        assert!(!load_order.is_ambiguous().unwrap());
1✔
1256
    }
1✔
1257

1258
    #[test]
1✔
1259
    fn is_ambiguous_should_ignore_loaded_early_loading_plugins_not_listed_in_active_plugins_file() {
1✔
1260
        let tmp_dir = tempdir().unwrap();
1✔
1261
        let mut load_order = prepare(GameId::SkyrimSE, &tmp_dir.path());
1✔
1262

1✔
1263
        let loaded_plugin_names: Vec<&str> = load_order
1✔
1264
            .plugins
1✔
1265
            .iter()
1✔
1266
            .map(|plugin| plugin.name())
3✔
1267
            .collect();
1✔
1268

1✔
1269
        write_active_plugins_file(load_order.game_settings(), &loaded_plugin_names);
1✔
1270

1✔
1271
        copy_to_test_dir("Blank.esm", "Dawnguard.esm", &load_order.game_settings());
1✔
1272
        let plugin = Plugin::new("Dawnguard.esm", &load_order.game_settings()).unwrap();
1✔
1273
        load_order.plugins_mut().push(plugin);
1✔
1274

1✔
1275
        assert!(!load_order.is_ambiguous().unwrap());
1✔
1276
    }
1✔
1277

1278
    #[test]
1✔
1279
    fn is_ambiguous_should_return_true_if_there_are_loaded_plugins_not_in_active_plugins_file() {
1✔
1280
        let tmp_dir = tempdir().unwrap();
1✔
1281
        let load_order = prepare(GameId::SkyrimSE, &tmp_dir.path());
1✔
1282

1✔
1283
        let mut loaded_plugin_names: Vec<&str> = load_order
1✔
1284
            .plugins
1✔
1285
            .iter()
1✔
1286
            .map(|plugin| plugin.name())
3✔
1287
            .collect();
1✔
1288

1✔
1289
        loaded_plugin_names.pop();
1✔
1290

1✔
1291
        write_active_plugins_file(load_order.game_settings(), &loaded_plugin_names);
1✔
1292

1✔
1293
        assert!(load_order.is_ambiguous().unwrap());
1✔
1294
    }
1✔
1295

1296
    #[test]
1✔
1297
    fn is_ambiguous_should_ignore_the_active_plugins_file_for_fallout4_when_test_files_are_configured(
1✔
1298
    ) {
1✔
1299
        let tmp_dir = tempdir().unwrap();
1✔
1300

1✔
1301
        let ini_path = tmp_dir.path().join("my games/Fallout4.ini");
1✔
1302
        create_parent_dirs(&ini_path).unwrap();
1✔
1303
        std::fs::write(&ini_path, "[General]\nsTestFile1=Blank.esp").unwrap();
1✔
1304

1✔
1305
        let load_order = prepare(GameId::Fallout4, &tmp_dir.path());
1✔
1306

1✔
1307
        write_active_plugins_file(load_order.game_settings(), &load_order.plugin_names());
1✔
1308

1✔
1309
        assert!(load_order.is_ambiguous().unwrap());
1✔
1310
    }
1✔
1311

1312
    #[test]
1✔
1313
    fn activate_should_check_normal_plugins_and_light_masters_active_limits_separately() {
1✔
1314
        let tmp_dir = tempdir().unwrap();
1✔
1315
        let mut load_order = prepare(GameId::SkyrimSE, &tmp_dir.path());
1✔
1316

1✔
1317
        let plugins = prepare_bulk_plugins(load_order.game_settings());
1✔
1318

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

1✔
1322
        load_order.load().unwrap();
1✔
1323
        assert!(load_order.set_active_plugins(&plugin_refs).is_ok());
1✔
1324

1325
        let i = 4356;
1✔
1326
        assert!(load_order.activate(&plugins[i]).is_ok());
1✔
1327
        assert!(load_order.is_active(&plugins[i]));
1✔
1328

1329
        let i = 254;
1✔
1330
        assert!(load_order.activate(&plugins[i]).is_ok());
1✔
1331
        assert!(load_order.is_active(&plugins[i]));
1✔
1332

1333
        let i = 256;
1✔
1334
        assert!(load_order.activate(&plugins[i]).is_err());
1✔
1335
        assert!(!load_order.is_active(&plugins[i]));
1✔
1336

1337
        let i = 4357;
1✔
1338
        assert!(load_order.activate(&plugins[i]).is_err());
1✔
1339
        assert!(!load_order.is_active(&plugins[i]));
1✔
1340
    }
1✔
1341

1342
    #[test]
1✔
1343
    fn set_active_plugins_should_count_light_masters_and_normal_plugins_separately() {
1✔
1344
        let tmp_dir = tempdir().unwrap();
1✔
1345
        let mut load_order = prepare(GameId::SkyrimSE, &tmp_dir.path());
1✔
1346

1✔
1347
        let plugins = prepare_bulk_plugins(load_order.game_settings());
1✔
1348

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

1✔
1352
        load_order.load().unwrap();
1✔
1353
        assert!(load_order.set_active_plugins(&plugin_refs).is_ok());
1✔
1354
        assert_eq!(4351, load_order.active_plugin_names().len());
1✔
1355
    }
1✔
1356

1357
    #[test]
1✔
1358
    fn set_active_plugins_should_error_if_given_more_than_4096_light_masters() {
1✔
1359
        let tmp_dir = tempdir().unwrap();
1✔
1360
        let mut load_order = prepare(GameId::SkyrimSE, &tmp_dir.path());
1✔
1361

1✔
1362
        let plugins = prepare_bulk_plugins(load_order.game_settings());
1✔
1363

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

1✔
1367
        assert!(load_order.set_active_plugins(&plugin_refs).is_err());
1✔
1368
        assert_eq!(1, load_order.active_plugin_names().len());
1✔
1369
    }
1✔
1370
}
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