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

Ortham / libloadorder / 10254629017

05 Aug 2024 07:11PM UTC coverage: 91.872% (+0.04%) from 91.834%
10254629017

push

github

Ortham
Fix handling of blueprint plugins

Blueprint plugins were introduced by Starfield.

Plugins that are both blueprint-flagged and master-flagged get loaded after all other plugins and aren't hoisted by non-blueprint plugins.

Some validation of blueprint plugin positions is still missing, because that requires a new Error enum variant, which is a breaking change.

511 of 516 new or added lines in 4 files covered. (99.03%)

96 existing lines in 6 files now uncovered.

7856 of 8551 relevant lines covered (91.87%)

167394.98 hits per line

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

99.27
/src/load_order/writable.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::create_dir_all;
21
use std::path::Path;
22

23
use unicase::{eq, UniCase};
24

25
use super::mutable::MutableLoadOrder;
26
use super::readable::{ReadableLoadOrder, ReadableLoadOrderBase};
27
use crate::enums::Error;
28
use crate::plugin::Plugin;
29
use crate::GameSettings;
30

31
const MAX_ACTIVE_FULL_PLUGINS: usize = 255;
32
const MAX_ACTIVE_LIGHT_PLUGINS: usize = 4096;
33
const MAX_ACTIVE_MEDIUM_PLUGINS: usize = 256;
34

35
pub trait WritableLoadOrder: ReadableLoadOrder {
36
    fn game_settings_mut(&mut self) -> &mut GameSettings;
37

38
    fn load(&mut self) -> Result<(), Error>;
39

40
    fn save(&mut self) -> Result<(), Error>;
41

42
    fn add(&mut self, plugin_name: &str) -> Result<usize, Error>;
43

44
    fn remove(&mut self, plugin_name: &str) -> Result<(), Error>;
45

46
    fn set_load_order(&mut self, plugin_names: &[&str]) -> Result<(), Error>;
47

48
    fn set_plugin_index(&mut self, plugin_name: &str, position: usize) -> Result<usize, Error>;
49

50
    fn is_self_consistent(&self) -> Result<bool, Error>;
51

52
    fn is_ambiguous(&self) -> Result<bool, Error>;
53

54
    fn activate(&mut self, plugin_name: &str) -> Result<(), Error>;
55

56
    fn deactivate(&mut self, plugin_name: &str) -> Result<(), Error>;
57

58
    fn set_active_plugins(&mut self, active_plugin_names: &[&str]) -> Result<(), Error>;
59
}
60

61
pub fn add<T: MutableLoadOrder>(load_order: &mut T, plugin_name: &str) -> Result<usize, Error> {
17✔
62
    match load_order.index_of(plugin_name) {
17✔
63
        Some(_) => Err(Error::DuplicatePlugin(plugin_name.to_string())),
1✔
64
        None => {
65
            let plugin = Plugin::new(plugin_name, load_order.game_settings())?;
16✔
66

67
            match load_order.insert_position(&plugin) {
15✔
68
                Some(position) => {
12✔
69
                    load_order.validate_index(&plugin, position)?;
12✔
70
                    load_order.plugins_mut().insert(position, plugin);
11✔
71
                    Ok(position)
11✔
72
                }
73
                None => {
74
                    load_order.validate_index(&plugin, load_order.plugins().len())?;
3✔
75
                    load_order.plugins_mut().push(plugin);
3✔
76
                    Ok(load_order.plugins().len() - 1)
3✔
77
                }
78
            }
79
        }
80
    }
81
}
17✔
82

83
pub fn remove<T: MutableLoadOrder>(load_order: &mut T, plugin_name: &str) -> Result<(), Error> {
5✔
84
    match load_order.index_of(plugin_name) {
5✔
85
        Some(index) => {
4✔
86
            let plugin_path = load_order.game_settings().plugin_path(plugin_name);
4✔
87
            if plugin_path.exists() {
4✔
88
                return Err(Error::InstalledPlugin(plugin_name.to_string()));
1✔
89
            }
3✔
90

3✔
91
            // If this is a master file that depends on a non-master file, it shouldn't be removed
3✔
92
            // without first moving the non-master file later in the load order, unless the next
3✔
93
            // master file also depends on that same non-master file. The non-master file also
3✔
94
            // doesn't need to be moved if this is the last master file in the load order.
3✔
95
            let plugin = &load_order.plugins()[index];
3✔
96
            if plugin.is_master_file() {
3✔
97
                let next_master = &load_order
2✔
98
                    .plugins()
2✔
99
                    .iter()
2✔
100
                    .skip(index + 1)
2✔
101
                    .find(|p| p.is_master_file());
2✔
102

103
                if let Some(next_master) = next_master {
2✔
104
                    let next_master_masters = next_master.masters()?;
2✔
105
                    let next_master_master_names: HashSet<_> =
2✔
106
                        next_master_masters.iter().map(UniCase::new).collect();
2✔
107

108
                    let mut masters = plugin.masters()?;
2✔
109

110
                    // Remove any masters that are also masters of the next master plugin.
111
                    masters.retain(|m| !next_master_master_names.contains(&UniCase::new(m)));
2✔
112

113
                    // Finally, check if any remaining masters are non-master plugins.
114
                    if let Some(n) = masters.iter().find(|n| {
2✔
115
                        load_order
2✔
116
                            .plugins()
2✔
117
                            .iter()
2✔
118
                            .find(|p| p.name_matches(n))
7✔
119
                            .map(|p| !p.is_master_file())
2✔
120
                            // If the master isn't installed, assume it's a master file and so
2✔
121
                            // doesn't prevent removal of the target plugin.
2✔
122
                            .unwrap_or(false)
2✔
123
                    }) {
2✔
124
                        return Err(Error::NonMasterBeforeMaster {
1✔
125
                            master: plugin_name.to_string(),
1✔
126
                            non_master: n.to_string(),
1✔
127
                        });
1✔
128
                    }
1✔
129
                }
×
130
            }
1✔
131

132
            load_order.plugins_mut().remove(index);
2✔
133

2✔
134
            Ok(())
2✔
135
        }
136
        None => Err(Error::PluginNotFound(plugin_name.to_string())),
1✔
137
    }
138
}
5✔
139

140
#[derive(Clone, Copy, Debug, Default)]
141
struct PluginCounts {
142
    light: usize,
143
    medium: usize,
144
    full: usize,
145
}
146

147
impl PluginCounts {
148
    fn count_plugin(&mut self, plugin: &Plugin) {
271,720✔
149
        if plugin.is_light_plugin() {
271,720✔
150
            self.light += 1;
36,869✔
151
        } else if plugin.is_medium_plugin() {
234,851✔
152
            self.medium += 1;
2,311✔
153
        } else {
232,540✔
154
            self.full += 1;
232,540✔
155
        }
232,540✔
156
    }
271,720✔
157

158
    fn max_active_full_plugins(&self) -> usize {
1,800✔
159
        let modifier = if self.medium > 0 && self.light > 0 {
1,800✔
160
            2
8✔
161
        } else if self.medium > 0 || self.light > 0 {
1,792✔
162
            1
10✔
163
        } else {
164
            0
1,782✔
165
        };
166
        MAX_ACTIVE_FULL_PLUGINS - modifier
1,800✔
167
    }
1,800✔
168
}
169

170
fn count_active_plugins<T: ReadableLoadOrderBase>(load_order: &T) -> PluginCounts {
1,796✔
171
    let mut counts = PluginCounts::default();
1,796✔
172

173
    for plugin in load_order.plugins().iter().filter(|p| p.is_active()) {
501,342✔
174
        counts.count_plugin(plugin);
256,109✔
175
    }
256,109✔
176

177
    counts
1,796✔
178
}
1,796✔
179

180
fn count_plugins(existing_plugins: &[Plugin], existing_plugin_indexes: &[usize]) -> PluginCounts {
15✔
181
    let mut counts = PluginCounts::default();
15✔
182

183
    for index in existing_plugin_indexes {
15,626✔
184
        let plugin = &existing_plugins[*index];
15,611✔
185
        counts.count_plugin(plugin);
15,611✔
186
    }
15,611✔
187

188
    counts
15✔
189
}
15✔
190

191
pub fn activate<T: MutableLoadOrder>(load_order: &mut T, plugin_name: &str) -> Result<(), Error> {
1,796✔
192
    let counts = count_active_plugins(load_order);
1,796✔
193

194
    let plugin = match load_order
1,796✔
195
        .plugins_mut()
1,796✔
196
        .iter_mut()
1,796✔
197
        .find(|p| p.name_matches(plugin_name))
241,120✔
198
    {
199
        Some(p) => p,
1,794✔
200
        None => return Err(Error::PluginNotFound(plugin_name.to_string())),
2✔
201
    };
202

203
    if !plugin.is_active() {
1,794✔
204
        let is_light = plugin.is_light_plugin();
1,793✔
205
        let is_medium = plugin.is_medium_plugin();
1,793✔
206
        let is_full = !is_light && !is_medium;
1,793✔
207

208
        if (is_light && counts.light == MAX_ACTIVE_LIGHT_PLUGINS)
1,793✔
209
            || (is_medium && counts.medium == MAX_ACTIVE_MEDIUM_PLUGINS)
1,792✔
210
            || (is_full && counts.full == counts.max_active_full_plugins())
1,791✔
211
        {
212
            return Err(Error::TooManyActivePlugins {
9✔
213
                light_count: counts.light,
9✔
214
                medium_count: counts.medium,
9✔
215
                full_count: counts.full,
9✔
216
            });
9✔
217
        } else {
218
            plugin.activate()?;
1,784✔
219
        }
220
    }
1✔
221

222
    Ok(())
1,785✔
223
}
1,796✔
224

225
pub fn deactivate<T: MutableLoadOrder>(load_order: &mut T, plugin_name: &str) -> Result<(), Error> {
5✔
226
    if load_order.game_settings().is_implicitly_active(plugin_name) {
5✔
227
        return Err(Error::ImplicitlyActivePlugin(plugin_name.to_string()));
2✔
228
    }
3✔
229

3✔
230
    load_order
3✔
231
        .plugins_mut()
3✔
232
        .iter_mut()
3✔
233
        .find(|p| p.name_matches(plugin_name))
8✔
234
        .ok_or_else(|| Error::PluginNotFound(plugin_name.to_string()))
3✔
235
        .map(|p| p.deactivate())
3✔
236
}
5✔
237

238
pub fn set_active_plugins<T: MutableLoadOrder>(
18✔
239
    load_order: &mut T,
18✔
240
    active_plugin_names: &[&str],
18✔
241
) -> Result<(), Error> {
18✔
242
    let existing_plugin_indices = load_order.lookup_plugins(active_plugin_names)?;
18✔
243

244
    let counts = count_plugins(load_order.plugins(), &existing_plugin_indices);
15✔
245

15✔
246
    if counts.full > counts.max_active_full_plugins()
15✔
247
        || counts.medium > MAX_ACTIVE_MEDIUM_PLUGINS
10✔
248
        || counts.light > MAX_ACTIVE_LIGHT_PLUGINS
9✔
249
    {
250
        return Err(Error::TooManyActivePlugins {
7✔
251
            light_count: counts.light,
7✔
252
            medium_count: counts.medium,
7✔
253
            full_count: counts.full,
7✔
254
        });
7✔
255
    }
8✔
256

257
    for plugin_name in load_order.game_settings().implicitly_active_plugins() {
41✔
258
        // If the plugin isn't installed, don't check that it's in the active
259
        // plugins list. Installed plugins will have already been loaded.
260
        if load_order.index_of(plugin_name).is_some()
41✔
261
            && !active_plugin_names.iter().any(|p| eq(*p, plugin_name))
6✔
262
        {
263
            return Err(Error::ImplicitlyActivePlugin(plugin_name.to_string()));
1✔
264
        }
40✔
265
    }
266

267
    load_order.deactivate_all();
7✔
268

269
    for index in existing_plugin_indices {
9,981✔
270
        load_order.plugins_mut()[index].activate()?;
9,974✔
271
    }
272

273
    Ok(())
7✔
274
}
18✔
275

276
pub fn create_parent_dirs(path: &Path) -> Result<(), Error> {
33✔
277
    if let Some(x) = path.parent() {
33✔
278
        if !x.exists() {
33✔
279
            create_dir_all(x).map_err(|e| Error::IoError(x.to_path_buf(), e))?
14✔
280
        }
19✔
281
    }
×
282
    Ok(())
33✔
283
}
33✔
284

285
#[cfg(test)]
286
mod tests {
287
    use super::*;
288

289
    use std::fs::remove_file;
290
    use std::path::Path;
291

292
    use rayon::prelude::*;
293
    use tempfile::tempdir;
294

295
    use crate::enums::GameId;
296
    use crate::game_settings::GameSettings;
297
    use crate::load_order::mutable::MutableLoadOrder;
298
    use crate::load_order::readable::{ReadableLoadOrder, ReadableLoadOrderBase};
299
    use crate::load_order::tests::{
300
        load_and_insert, mock_game_files, set_blueprint_flag, set_master_flag,
301
    };
302
    use crate::tests::copy_to_test_dir;
303

304
    struct TestLoadOrder {
305
        game_settings: GameSettings,
306
        plugins: Vec<Plugin>,
307
    }
308

309
    impl ReadableLoadOrderBase for TestLoadOrder {
310
        fn game_settings_base(&self) -> &GameSettings {
58,359✔
311
            &self.game_settings
58,359✔
312
        }
58,359✔
313

314
        fn plugins(&self) -> &[Plugin] {
56,472✔
315
            &self.plugins
56,472✔
316
        }
56,472✔
317
    }
318

319
    impl MutableLoadOrder for TestLoadOrder {
320
        fn plugins_mut(&mut self) -> &mut Vec<Plugin> {
31,227✔
321
            &mut self.plugins
31,227✔
322
        }
31,227✔
323
    }
324

325
    fn prepare(game_id: GameId, game_dir: &Path) -> TestLoadOrder {
43✔
326
        let (game_settings, plugins) = mock_game_files(game_id, game_dir);
43✔
327
        TestLoadOrder {
43✔
328
            game_settings,
43✔
329
            plugins,
43✔
330
        }
43✔
331
    }
43✔
332

333
    fn insert<T: MutableLoadOrder>(load_order: &mut T, plugin: Plugin) {
19,420✔
334
        match load_order.insert_position(&plugin) {
19,420✔
335
            Some(position) => {
19,420✔
336
                load_order.plugins_mut().insert(position, plugin);
19,420✔
337
            }
19,420✔
UNCOV
338
            None => {
×
UNCOV
339
                load_order.plugins_mut().push(plugin);
×
340
            }
×
341
        }
342
    }
19,420✔
343

344
    fn prepare_bulk_plugins<F>(
20✔
345
        load_order: &mut TestLoadOrder,
20✔
346
        source_plugin_name: &str,
20✔
347
        plugin_count: usize,
20✔
348
        name_generator: F,
20✔
349
    ) -> Vec<String>
20✔
350
    where
20✔
351
        F: Fn(usize) -> String,
20✔
352
    {
20✔
353
        let names: Vec<_> = (0..plugin_count).map(name_generator).collect();
20✔
354

20✔
355
        let plugins: Vec<_> = names
20✔
356
            .par_iter()
20✔
357
            .map(|name| {
19,420✔
358
                copy_to_test_dir(source_plugin_name, &name, load_order.game_settings());
19,420✔
359
                Plugin::new(&name, load_order.game_settings()).unwrap()
19,420✔
360
            })
19,420✔
361
            .collect();
20✔
362

363
        for plugin in plugins {
19,440✔
364
            insert(load_order, plugin);
19,420✔
365
        }
19,420✔
366

367
        names
20✔
368
    }
20✔
369

370
    fn prepare_bulk_full_plugins(load_order: &mut TestLoadOrder) -> Vec<String> {
14✔
371
        let plugin_name = if load_order.game_settings.id() == GameId::Starfield {
14✔
372
            "Blank.full.esm"
12✔
373
        } else {
374
            "Blank.esm"
2✔
375
        };
376
        prepare_bulk_plugins(load_order, plugin_name, 260, |i| {
3,640✔
377
            format!("Blank{}.full.esm", i)
3,640✔
378
        })
3,640✔
379
    }
14✔
380

381
    fn prepare_bulk_medium_plugins(load_order: &mut TestLoadOrder) -> Vec<String> {
3✔
382
        prepare_bulk_plugins(load_order, "Blank.medium.esm", 260, |i| {
780✔
383
            format!("Blank{}.medium.esm", i)
780✔
384
        })
780✔
385
    }
3✔
386

387
    fn prepare_bulk_light_plugins(load_order: &mut TestLoadOrder) -> Vec<String> {
3✔
388
        prepare_bulk_plugins(load_order, "Blank.small.esm", 5000, |i| {
15,000✔
389
            format!("Blank{}.small.esm", i)
15,000✔
390
        })
15,000✔
391
    }
3✔
392

393
    #[test]
394
    fn add_should_error_if_the_plugin_is_already_in_the_load_order() {
1✔
395
        let tmp_dir = tempdir().unwrap();
1✔
396
        let mut load_order = prepare(GameId::Oblivion, &tmp_dir.path());
1✔
397

1✔
398
        assert!(add(&mut load_order, "Blank.esm").is_ok());
1✔
399
        assert!(add(&mut load_order, "Blank.esm").is_err());
1✔
400
    }
1✔
401

402
    #[test]
403
    fn add_should_error_if_given_a_master_that_would_hoist_a_non_master() {
1✔
404
        let tmp_dir = tempdir().unwrap();
1✔
405
        let mut load_order = prepare(GameId::Oblivion, &tmp_dir.path());
1✔
406

1✔
407
        let plugins_dir = &load_order.game_settings().plugins_directory();
1✔
408
        copy_to_test_dir(
1✔
409
            "Blank - Different.esm",
1✔
410
            "Blank - Different.esm",
1✔
411
            load_order.game_settings(),
1✔
412
        );
1✔
413
        set_master_flag(
1✔
414
            GameId::Oblivion,
1✔
415
            &plugins_dir.join("Blank - Different.esm"),
1✔
416
            false,
1✔
417
        )
1✔
418
        .unwrap();
1✔
419
        assert!(add(&mut load_order, "Blank - Different.esm").is_ok());
1✔
420

421
        copy_to_test_dir(
1✔
422
            "Blank - Different Master Dependent.esm",
1✔
423
            "Blank - Different Master Dependent.esm",
1✔
424
            load_order.game_settings(),
1✔
425
        );
1✔
426

1✔
427
        assert!(add(&mut load_order, "Blank - Different Master Dependent.esm").is_err());
1✔
428
    }
1✔
429

430
    #[test]
431
    fn add_should_error_if_the_plugin_is_not_valid() {
1✔
432
        let tmp_dir = tempdir().unwrap();
1✔
433
        let mut load_order = prepare(GameId::Oblivion, &tmp_dir.path());
1✔
434

1✔
435
        assert!(add(&mut load_order, "invalid.esm").is_err());
1✔
436
    }
1✔
437

438
    #[test]
439
    fn add_should_insert_a_master_before_non_masters() {
1✔
440
        let tmp_dir = tempdir().unwrap();
1✔
441
        let mut load_order = prepare(GameId::Oblivion, &tmp_dir.path());
1✔
442

1✔
443
        assert_eq!(1, add(&mut load_order, "Blank.esm").unwrap());
1✔
444
        assert_eq!(1, load_order.index_of("Blank.esm").unwrap());
1✔
445
    }
1✔
446

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

1✔
452
        assert_eq!(
1✔
453
            3,
1✔
454
            add(&mut load_order, "Blank - Master Dependent.esp").unwrap()
1✔
455
        );
1✔
456
        assert_eq!(
1✔
457
            3,
1✔
458
            load_order.index_of("Blank - Master Dependent.esp").unwrap()
1✔
459
        );
1✔
460
    }
1✔
461

462
    #[test]
463
    fn add_should_hoist_a_non_master_that_a_master_depends_on() {
1✔
464
        let tmp_dir = tempdir().unwrap();
1✔
465
        let mut load_order = prepare(GameId::Oblivion, &tmp_dir.path());
1✔
466

1✔
467
        let plugins_dir = &load_order.game_settings().plugins_directory();
1✔
468
        copy_to_test_dir(
1✔
469
            "Blank - Different Master Dependent.esm",
1✔
470
            "Blank - Different Master Dependent.esm",
1✔
471
            load_order.game_settings(),
1✔
472
        );
1✔
473
        assert!(add(&mut load_order, "Blank - Different Master Dependent.esm").is_ok());
1✔
474

475
        copy_to_test_dir(
1✔
476
            "Blank - Different.esm",
1✔
477
            "Blank - Different.esm",
1✔
478
            load_order.game_settings(),
1✔
479
        );
1✔
480
        set_master_flag(
1✔
481
            GameId::Oblivion,
1✔
482
            &plugins_dir.join("Blank - Different.esm"),
1✔
483
            false,
1✔
484
        )
1✔
485
        .unwrap();
1✔
486
        assert_eq!(1, add(&mut load_order, "Blank - Different.esm").unwrap());
1✔
487
    }
1✔
488

489
    #[test]
490
    fn add_should_hoist_a_master_that_a_master_depends_on() {
1✔
491
        let tmp_dir = tempdir().unwrap();
1✔
492
        let mut load_order = prepare(GameId::Oblivion, &tmp_dir.path());
1✔
493

1✔
494
        let plugin_name = "Blank - Master Dependent.esm";
1✔
495
        copy_to_test_dir(plugin_name, plugin_name, load_order.game_settings());
1✔
496
        assert_eq!(1, add(&mut load_order, plugin_name).unwrap());
1✔
497

498
        assert_eq!(1, add(&mut load_order, "Blank.esm").unwrap());
1✔
499
    }
1✔
500

501
    #[test]
502
    fn remove_should_error_if_the_plugin_is_not_in_the_load_order() {
1✔
503
        let tmp_dir = tempdir().unwrap();
1✔
504
        let mut load_order = prepare(GameId::Oblivion, &tmp_dir.path());
1✔
505
        assert!(remove(&mut load_order, "Blank.esm").is_err());
1✔
506
    }
1✔
507

508
    #[test]
509
    fn remove_should_error_if_the_plugin_is_installed() {
1✔
510
        let tmp_dir = tempdir().unwrap();
1✔
511
        let mut load_order = prepare(GameId::Oblivion, &tmp_dir.path());
1✔
512
        assert!(remove(&mut load_order, "Blank.esp").is_err());
1✔
513
    }
1✔
514

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

1✔
521
        let plugin_to_remove = "Blank - Different Master Dependent.esm";
1✔
522

1✔
523
        let plugins_dir = &load_order.game_settings().plugins_directory();
1✔
524
        copy_to_test_dir(
1✔
525
            plugin_to_remove,
1✔
526
            plugin_to_remove,
1✔
527
            load_order.game_settings(),
1✔
528
        );
1✔
529
        assert!(add(&mut load_order, plugin_to_remove).is_ok());
1✔
530

531
        copy_to_test_dir(
1✔
532
            "Blank - Different.esm",
1✔
533
            "Blank - Different.esm",
1✔
534
            load_order.game_settings(),
1✔
535
        );
1✔
536
        set_master_flag(
1✔
537
            GameId::Oblivion,
1✔
538
            &plugins_dir.join("Blank - Different.esm"),
1✔
539
            false,
1✔
540
        )
1✔
541
        .unwrap();
1✔
542
        assert_eq!(1, add(&mut load_order, "Blank - Different.esm").unwrap());
1✔
543

544
        copy_to_test_dir(
1✔
545
            "Blank - Master Dependent.esm",
1✔
546
            "Blank - Master Dependent.esm",
1✔
547
            load_order.game_settings(),
1✔
548
        );
1✔
549
        assert!(add(&mut load_order, "Blank - Master Dependent.esm").is_ok());
1✔
550

551
        let blank_master_dependent = load_order.plugins.remove(1);
1✔
552
        load_order.plugins.insert(3, blank_master_dependent);
1✔
553

1✔
554
        std::fs::remove_file(&plugins_dir.join(plugin_to_remove)).unwrap();
1✔
555

1✔
556
        match remove(&mut load_order, plugin_to_remove).unwrap_err() {
1✔
557
            Error::NonMasterBeforeMaster { master, non_master } => {
1✔
558
                assert_eq!("Blank - Different Master Dependent.esm", master);
1✔
559
                assert_eq!("Blank - Different.esm", non_master);
1✔
560
            }
UNCOV
561
            e => panic!("Unexpected error type: {:?}", e),
×
562
        }
563
    }
1✔
564

565
    #[test]
566
    fn remove_should_allow_removal_of_a_master_that_depends_on_a_blueprint_plugin() {
1✔
567
        let tmp_dir = tempdir().unwrap();
1✔
568
        let mut load_order = prepare(GameId::Starfield, &tmp_dir.path());
1✔
569

1✔
570
        let plugins_dir = &load_order.game_settings().plugins_directory();
1✔
571

1✔
572
        let plugin_to_remove = "Blank - Override.full.esm";
1✔
573
        copy_to_test_dir(
1✔
574
            plugin_to_remove,
1✔
575
            plugin_to_remove,
1✔
576
            load_order.game_settings(),
1✔
577
        );
1✔
578
        assert!(add(&mut load_order, plugin_to_remove).is_ok());
1✔
579

580
        let blueprint_plugin = "Blank.full.esm";
1✔
581
        copy_to_test_dir(
1✔
582
            blueprint_plugin,
1✔
583
            blueprint_plugin,
1✔
584
            load_order.game_settings(),
1✔
585
        );
1✔
586
        set_blueprint_flag(GameId::Starfield, &plugins_dir.join(blueprint_plugin), true).unwrap();
1✔
587
        assert_eq!(3, add(&mut load_order, blueprint_plugin).unwrap());
1✔
588

589
        let following_master_plugin = "Blank.medium.esm";
1✔
590
        copy_to_test_dir(
1✔
591
            following_master_plugin,
1✔
592
            following_master_plugin,
1✔
593
            load_order.game_settings(),
1✔
594
        );
1✔
595
        assert!(add(&mut load_order, following_master_plugin).is_ok());
1✔
596

597
        std::fs::remove_file(&plugins_dir.join(plugin_to_remove)).unwrap();
1✔
598

1✔
599
        assert!(remove(&mut load_order, plugin_to_remove).is_ok());
1✔
600
    }
1✔
601

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

1✔
607
        remove_file(
1✔
608
            load_order
1✔
609
                .game_settings()
1✔
610
                .plugins_directory()
1✔
611
                .join("Blank.esp"),
1✔
612
        )
1✔
613
        .unwrap();
1✔
614

1✔
615
        assert!(remove(&mut load_order, "Blank.esp").is_ok());
1✔
616
        assert!(load_order.index_of("Blank.esp").is_none());
1✔
617
    }
1✔
618

619
    #[test]
620
    fn activate_should_activate_the_plugin_with_the_given_filename() {
1✔
621
        let tmp_dir = tempdir().unwrap();
1✔
622
        let mut load_order = prepare(GameId::Oblivion, &tmp_dir.path());
1✔
623

1✔
624
        assert!(activate(&mut load_order, "Blank - Different.esp").is_ok());
1✔
625
        assert!(load_order.is_active("Blank - Different.esp"));
1✔
626
    }
1✔
627

628
    #[test]
629
    fn activate_should_error_if_the_plugin_is_not_valid() {
1✔
630
        let tmp_dir = tempdir().unwrap();
1✔
631
        let mut load_order = prepare(GameId::Oblivion, &tmp_dir.path());
1✔
632

1✔
633
        assert!(activate(&mut load_order, "missing.esp").is_err());
1✔
634
        assert!(load_order.index_of("missing.esp").is_none());
1✔
635
    }
1✔
636

637
    #[test]
638
    fn activate_should_error_if_the_plugin_is_not_already_in_the_load_order() {
1✔
639
        let tmp_dir = tempdir().unwrap();
1✔
640
        let mut load_order = prepare(GameId::Oblivion, &tmp_dir.path());
1✔
641

1✔
642
        assert!(activate(&mut load_order, "Blank.esm").is_err());
1✔
643
        assert!(!load_order.is_active("Blank.esm"));
1✔
644
    }
1✔
645

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

1✔
651
        assert!(activate(&mut load_order, "Blank - different.esp").is_ok());
1✔
652
        assert!(load_order.is_active("Blank - Different.esp"));
1✔
653
    }
1✔
654

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

1✔
660
        let plugins = prepare_bulk_full_plugins(&mut load_order);
1✔
661
        for plugin in &plugins[..MAX_ACTIVE_FULL_PLUGINS - 1] {
254✔
662
            activate(&mut load_order, &plugin).unwrap();
254✔
663
        }
254✔
664

665
        assert!(activate(&mut load_order, "Blank - Different.esp").is_err());
1✔
666
        assert!(!load_order.is_active("Blank - Different.esp"));
1✔
667
    }
1✔
668

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

1✔
674
        let plugins = prepare_bulk_full_plugins(&mut load_order);
1✔
675
        for plugin in &plugins[..MAX_ACTIVE_FULL_PLUGINS - 1] {
254✔
676
            activate(&mut load_order, &plugin).unwrap();
254✔
677
        }
254✔
678

679
        assert!(load_order.is_active("Blank.esp"));
1✔
680
        assert!(activate(&mut load_order, "Blank.esp").is_ok());
1✔
681
    }
1✔
682

683
    #[test]
684
    fn activate_should_fail_if_at_the_active_plugins_limit_and_the_plugin_is_an_update_plugin() {
1✔
685
        let tmp_dir = tempdir().unwrap();
1✔
686
        let mut load_order = prepare(GameId::Starfield, &tmp_dir.path());
1✔
687

1✔
688
        let plugins = prepare_bulk_full_plugins(&mut load_order);
1✔
689
        for plugin in &plugins[..MAX_ACTIVE_FULL_PLUGINS - 1] {
254✔
690
            activate(&mut load_order, &plugin).unwrap();
254✔
691
        }
254✔
692

693
        let plugin = "Blank - Override.esp";
1✔
694
        load_and_insert(&mut load_order, &plugin);
1✔
695

1✔
696
        assert!(!load_order.is_active(plugin));
1✔
697

698
        assert!(activate(&mut load_order, plugin).is_err());
1✔
699
        assert!(!load_order.is_active(plugin));
1✔
700
    }
1✔
701

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

1✔
707
        let plugins = prepare_bulk_full_plugins(&mut load_order);
1✔
708
        for plugin in &plugins[..MAX_ACTIVE_FULL_PLUGINS - 1] {
254✔
709
            activate(&mut load_order, &plugin).unwrap();
254✔
710
        }
254✔
711

712
        let plugin = "Blank - Override.esp";
1✔
713
        load_and_insert(&mut load_order, &plugin);
1✔
714

1✔
715
        assert!(!load_order.is_active(plugin));
1✔
716

717
        assert!(activate(&mut load_order, plugin).is_err());
1✔
718
        assert!(!load_order.is_active(plugin));
1✔
719
    }
1✔
720

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

1✔
726
        let plugins = prepare_bulk_full_plugins(&mut load_order);
1✔
727
        for plugin in &plugins[..MAX_ACTIVE_FULL_PLUGINS - 3] {
252✔
728
            activate(&mut load_order, &plugin).unwrap();
252✔
729
        }
252✔
730

731
        let plugin = "Blank.small.esm";
1✔
732
        load_and_insert(&mut load_order, plugin);
1✔
733
        activate(&mut load_order, plugin).unwrap();
1✔
734

1✔
735
        let plugin = &plugins[MAX_ACTIVE_FULL_PLUGINS - 2];
1✔
736
        assert!(!load_order.is_active(plugin));
1✔
737

738
        assert!(activate(&mut load_order, plugin).is_ok());
1✔
739
        assert!(load_order.is_active(plugin));
1✔
740

741
        let plugin = &plugins[MAX_ACTIVE_FULL_PLUGINS - 1];
1✔
742
        assert!(!load_order.is_active(plugin));
1✔
743

744
        assert!(activate(&mut load_order, plugin).is_err());
1✔
745
        assert!(!load_order.is_active(plugin));
1✔
746
    }
1✔
747

748
    #[test]
749
    fn activate_should_lower_the_full_plugin_limit_if_a_medium_plugin_is_present() {
1✔
750
        let tmp_dir = tempdir().unwrap();
1✔
751
        let mut load_order = prepare(GameId::Starfield, &tmp_dir.path());
1✔
752

1✔
753
        let plugins = prepare_bulk_full_plugins(&mut load_order);
1✔
754
        for plugin in &plugins[..MAX_ACTIVE_FULL_PLUGINS - 3] {
252✔
755
            activate(&mut load_order, &plugin).unwrap();
252✔
756
        }
252✔
757

758
        let plugin = "Blank.medium.esm";
1✔
759
        load_and_insert(&mut load_order, plugin);
1✔
760
        activate(&mut load_order, plugin).unwrap();
1✔
761

1✔
762
        let plugin = &plugins[MAX_ACTIVE_FULL_PLUGINS - 2];
1✔
763
        assert!(!load_order.is_active(plugin));
1✔
764

765
        assert!(activate(&mut load_order, plugin).is_ok());
1✔
766
        assert!(load_order.is_active(plugin));
1✔
767

768
        let plugin = &plugins[MAX_ACTIVE_FULL_PLUGINS - 1];
1✔
769
        assert!(!load_order.is_active(plugin));
1✔
770

771
        assert!(activate(&mut load_order, plugin).is_err());
1✔
772
        assert!(!load_order.is_active(plugin));
1✔
773
    }
1✔
774

775
    #[test]
776
    fn activate_should_lower_the_full_plugin_limit_if_light_and_medium_plugins_are_present() {
1✔
777
        let tmp_dir = tempdir().unwrap();
1✔
778
        let mut load_order = prepare(GameId::Starfield, &tmp_dir.path());
1✔
779

1✔
780
        let plugins = prepare_bulk_full_plugins(&mut load_order);
1✔
781
        for plugin in &plugins[..MAX_ACTIVE_FULL_PLUGINS - 4] {
251✔
782
            activate(&mut load_order, &plugin).unwrap();
251✔
783
        }
251✔
784

785
        for plugin in ["Blank.medium.esm", "Blank.small.esm"] {
2✔
786
            load_and_insert(&mut load_order, plugin);
2✔
787
            activate(&mut load_order, plugin).unwrap();
2✔
788
        }
2✔
789

790
        let plugin = &plugins[MAX_ACTIVE_FULL_PLUGINS - 3];
1✔
791
        assert!(!load_order.is_active(plugin));
1✔
792

793
        assert!(activate(&mut load_order, plugin).is_ok());
1✔
794
        assert!(load_order.is_active(plugin));
1✔
795

796
        let plugin = &plugins[MAX_ACTIVE_FULL_PLUGINS - 2];
1✔
797
        assert!(!load_order.is_active(plugin));
1✔
798

799
        assert!(activate(&mut load_order, plugin).is_err());
1✔
800
        assert!(!load_order.is_active(plugin));
1✔
801
    }
1✔
802

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

1✔
808
        let full = prepare_bulk_full_plugins(&mut load_order);
1✔
809
        let medium = prepare_bulk_medium_plugins(&mut load_order);
1✔
810
        let light = prepare_bulk_light_plugins(&mut load_order);
1✔
811

1✔
812
        let mut plugin_refs = vec!["Starfield.esm"];
1✔
813
        plugin_refs.extend(full[..251].iter().map(String::as_str));
1✔
814
        plugin_refs.extend(medium[..255].iter().map(String::as_str));
1✔
815
        plugin_refs.extend(light[..4095].iter().map(String::as_str));
1✔
816

1✔
817
        assert!(set_active_plugins(&mut load_order, &plugin_refs).is_ok());
1✔
818

819
        let plugin = &full[254];
1✔
820
        assert!(!load_order.is_active(plugin));
1✔
821
        assert!(activate(&mut load_order, plugin).is_ok());
1✔
822
        assert!(load_order.is_active(plugin));
1✔
823

824
        let plugin = &medium[255];
1✔
825
        assert!(!load_order.is_active(plugin));
1✔
826
        assert!(activate(&mut load_order, plugin).is_ok());
1✔
827
        assert!(load_order.is_active(plugin));
1✔
828

829
        let plugin = &light[4095];
1✔
830
        assert!(!load_order.is_active(plugin));
1✔
831
        assert!(activate(&mut load_order, plugin).is_ok());
1✔
832
        assert!(load_order.is_active(plugin));
1✔
833

834
        let plugin = &full[255];
1✔
835
        assert!(activate(&mut load_order, plugin).is_err());
1✔
836
        assert!(!load_order.is_active(plugin));
1✔
837

838
        let plugin = &medium[256];
1✔
839
        assert!(activate(&mut load_order, plugin).is_err());
1✔
840
        assert!(!load_order.is_active(plugin));
1✔
841

842
        let plugin = &light[4096];
1✔
843
        assert!(activate(&mut load_order, plugin).is_err());
1✔
844
        assert!(!load_order.is_active(plugin));
1✔
845
    }
1✔
846

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

1✔
852
        assert!(load_order.is_active("Blank.esp"));
1✔
853
        assert!(deactivate(&mut load_order, "Blank.esp").is_ok());
1✔
854
        assert!(!load_order.is_active("Blank.esp"));
1✔
855
    }
1✔
856

857
    #[test]
858
    fn deactivate_should_error_if_the_plugin_is_not_in_the_load_order() {
1✔
859
        let tmp_dir = tempdir().unwrap();
1✔
860
        let mut load_order = prepare(GameId::Oblivion, &tmp_dir.path());
1✔
861

1✔
862
        assert!(deactivate(&mut load_order, "missing.esp").is_err());
1✔
863
        assert!(load_order.index_of("missing.esp").is_none());
1✔
864
    }
1✔
865

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

1✔
871
        assert!(activate(&mut load_order, "Skyrim.esm").is_ok());
1✔
872
        assert!(deactivate(&mut load_order, "Skyrim.esm").is_err());
1✔
873
        assert!(load_order.is_active("Skyrim.esm"));
1✔
874
    }
1✔
875

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

1✔
881
        assert!(deactivate(&mut load_order, "Update.esm").is_err());
1✔
882
        assert!(load_order.index_of("Update.esm").is_none());
1✔
883
    }
1✔
884

885
    #[test]
886
    fn deactivate_should_do_nothing_if_the_plugin_is_inactive() {
1✔
887
        let tmp_dir = tempdir().unwrap();
1✔
888
        let mut load_order = prepare(GameId::Skyrim, &tmp_dir.path());
1✔
889

1✔
890
        assert!(!load_order.is_active("Blank - Different.esp"));
1✔
891
        assert!(deactivate(&mut load_order, "Blank - Different.esp").is_ok());
1✔
892
        assert!(!load_order.is_active("Blank - Different.esp"));
1✔
893
    }
1✔
894

895
    #[test]
896
    fn set_active_plugins_should_error_if_passed_an_invalid_plugin_name() {
1✔
897
        let tmp_dir = tempdir().unwrap();
1✔
898
        let mut load_order = prepare(GameId::Oblivion, &tmp_dir.path());
1✔
899

1✔
900
        let active_plugins = ["missing.esp"];
1✔
901
        assert!(set_active_plugins(&mut load_order, &active_plugins).is_err());
1✔
902
        assert_eq!(1, load_order.active_plugin_names().len());
1✔
903
    }
1✔
904

905
    #[test]
906
    fn set_active_plugins_should_error_if_the_given_plugins_are_missing_implicitly_active_plugins()
1✔
907
    {
1✔
908
        let tmp_dir = tempdir().unwrap();
1✔
909
        let mut load_order = prepare(GameId::Skyrim, &tmp_dir.path());
1✔
910

1✔
911
        let active_plugins = ["Blank.esp"];
1✔
912
        assert!(set_active_plugins(&mut load_order, &active_plugins).is_err());
1✔
913
        assert_eq!(1, load_order.active_plugin_names().len());
1✔
914
    }
1✔
915

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

1✔
921
        let active_plugins = ["Skyrim.esm", "Update.esm"];
1✔
922
        assert!(set_active_plugins(&mut load_order, &active_plugins).is_err());
1✔
923
        assert_eq!(1, load_order.active_plugin_names().len());
1✔
924
    }
1✔
925

926
    #[test]
927
    fn set_active_plugins_should_error_if_given_plugins_not_in_the_load_order() {
1✔
928
        let tmp_dir = tempdir().unwrap();
1✔
929
        let mut load_order = prepare(GameId::Oblivion, &tmp_dir.path());
1✔
930

1✔
931
        let active_plugins = ["Blank - Master Dependent.esp", "Blàñk.esp"];
1✔
932
        assert!(set_active_plugins(&mut load_order, &active_plugins).is_err());
1✔
933
        assert!(!load_order.is_active("Blank - Master Dependent.esp"));
1✔
934
        assert!(load_order
1✔
935
            .index_of("Blank - Master Dependent.esp")
1✔
936
            .is_none());
1✔
937
        assert!(!load_order.is_active("Blàñk.esp"));
1✔
938
        assert!(load_order.index_of("Blàñk.esp").is_none());
1✔
939
    }
1✔
940

941
    #[test]
942
    fn set_active_plugins_should_deactivate_all_plugins_not_given() {
1✔
943
        let tmp_dir = tempdir().unwrap();
1✔
944
        let mut load_order = prepare(GameId::Oblivion, &tmp_dir.path());
1✔
945

1✔
946
        let active_plugins = ["Blank - Different.esp"];
1✔
947
        assert!(load_order.is_active("Blank.esp"));
1✔
948
        assert!(set_active_plugins(&mut load_order, &active_plugins).is_ok());
1✔
949
        assert!(!load_order.is_active("Blank.esp"));
1✔
950
    }
1✔
951

952
    #[test]
953
    fn set_active_plugins_should_activate_all_given_plugins() {
1✔
954
        let tmp_dir = tempdir().unwrap();
1✔
955
        let mut load_order = prepare(GameId::Oblivion, &tmp_dir.path());
1✔
956

1✔
957
        let active_plugins = ["Blank - Different.esp"];
1✔
958
        assert!(!load_order.is_active("Blank - Different.esp"));
1✔
959
        assert!(set_active_plugins(&mut load_order, &active_plugins).is_ok());
1✔
960
        assert!(load_order.is_active("Blank - Different.esp"));
1✔
961
    }
1✔
962

963
    #[test]
964
    fn set_active_plugins_should_count_update_plugins_towards_limit() {
1✔
965
        let tmp_dir = tempdir().unwrap();
1✔
966
        let mut load_order = prepare(GameId::Starfield, &tmp_dir.path());
1✔
967

1✔
968
        let blank_override = "Blank - Override.esp";
1✔
969
        load_and_insert(&mut load_order, blank_override);
1✔
970

1✔
971
        let mut active_plugins = vec!["Starfield.esm".to_string(), blank_override.to_string()];
1✔
972

1✔
973
        let plugins = prepare_bulk_full_plugins(&mut load_order);
1✔
974
        for plugin in plugins.into_iter().take(MAX_ACTIVE_FULL_PLUGINS - 1) {
254✔
975
            active_plugins.push(plugin);
254✔
976
        }
254✔
977

978
        let active_plugins: Vec<&str> = active_plugins.iter().map(|s| s.as_str()).collect();
256✔
979

1✔
980
        assert!(set_active_plugins(&mut load_order, &active_plugins).is_err());
1✔
981
        assert_eq!(1, load_order.active_plugin_names().len());
1✔
982
    }
1✔
983

984
    #[test]
985
    fn set_active_plugins_should_lower_the_full_plugin_limit_if_a_light_plugin_is_present() {
1✔
986
        let tmp_dir = tempdir().unwrap();
1✔
987
        let mut load_order = prepare(GameId::Starfield, &tmp_dir.path());
1✔
988

1✔
989
        let full = prepare_bulk_full_plugins(&mut load_order);
1✔
990

1✔
991
        let plugin = "Blank.small.esm";
1✔
992
        load_and_insert(&mut load_order, plugin);
1✔
993

1✔
994
        let mut plugin_refs = vec!["Starfield.esm", plugin];
1✔
995
        plugin_refs.extend(full[..253].iter().map(String::as_str));
1✔
996

1✔
997
        assert!(set_active_plugins(&mut load_order, &plugin_refs).is_ok());
1✔
998
        assert_eq!(255, load_order.active_plugin_names().len());
1✔
999

1000
        plugin_refs.push(full[253].as_str());
1✔
1001

1✔
1002
        assert!(set_active_plugins(&mut load_order, &plugin_refs).is_err());
1✔
1003
        assert_eq!(255, load_order.active_plugin_names().len());
1✔
1004
    }
1✔
1005

1006
    #[test]
1007
    fn set_active_plugins_should_lower_the_full_plugin_limit_if_a_medium_plugin_is_present() {
1✔
1008
        let tmp_dir = tempdir().unwrap();
1✔
1009
        let mut load_order = prepare(GameId::Starfield, &tmp_dir.path());
1✔
1010

1✔
1011
        let full = prepare_bulk_full_plugins(&mut load_order);
1✔
1012

1✔
1013
        let plugin = "Blank.medium.esm";
1✔
1014
        load_and_insert(&mut load_order, plugin);
1✔
1015

1✔
1016
        let mut plugin_refs = vec!["Starfield.esm", plugin];
1✔
1017
        plugin_refs.extend(full[..253].iter().map(String::as_str));
1✔
1018

1✔
1019
        assert!(set_active_plugins(&mut load_order, &plugin_refs).is_ok());
1✔
1020
        assert_eq!(255, load_order.active_plugin_names().len());
1✔
1021

1022
        plugin_refs.push(full[253].as_str());
1✔
1023

1✔
1024
        assert!(set_active_plugins(&mut load_order, &plugin_refs).is_err());
1✔
1025
        assert_eq!(255, load_order.active_plugin_names().len());
1✔
1026
    }
1✔
1027

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

1✔
1033
        let full = prepare_bulk_full_plugins(&mut load_order);
1✔
1034

1✔
1035
        let medium_plugin = "Blank.medium.esm";
1✔
1036
        let light_plugin = "Blank.small.esm";
1✔
1037
        load_and_insert(&mut load_order, medium_plugin);
1✔
1038
        load_and_insert(&mut load_order, light_plugin);
1✔
1039

1✔
1040
        let mut plugin_refs = vec!["Starfield.esm", medium_plugin, light_plugin];
1✔
1041
        plugin_refs.extend(full[..252].iter().map(String::as_str));
1✔
1042

1✔
1043
        assert!(set_active_plugins(&mut load_order, &plugin_refs).is_ok());
1✔
1044
        assert_eq!(255, load_order.active_plugin_names().len());
1✔
1045

1046
        plugin_refs.push(full[252].as_str());
1✔
1047

1✔
1048
        assert!(set_active_plugins(&mut load_order, &plugin_refs).is_err());
1✔
1049
        assert_eq!(255, load_order.active_plugin_names().len());
1✔
1050
    }
1✔
1051

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

1✔
1057
        let full = prepare_bulk_full_plugins(&mut load_order);
1✔
1058
        let medium = prepare_bulk_medium_plugins(&mut load_order);
1✔
1059
        let light = prepare_bulk_light_plugins(&mut load_order);
1✔
1060

1✔
1061
        let mut plugin_refs = vec!["Starfield.esm"];
1✔
1062
        plugin_refs.extend(full[..252].iter().map(String::as_str));
1✔
1063
        plugin_refs.extend(medium[..256].iter().map(String::as_str));
1✔
1064
        plugin_refs.extend(light[..4096].iter().map(String::as_str));
1✔
1065

1✔
1066
        assert!(set_active_plugins(&mut load_order, &plugin_refs).is_ok());
1✔
1067
        assert_eq!(4605, load_order.active_plugin_names().len());
1✔
1068
    }
1✔
1069

1070
    #[test]
1071
    fn set_active_plugins_should_error_if_given_more_than_255_full_plugins() {
1✔
1072
        let tmp_dir = tempdir().unwrap();
1✔
1073
        let mut load_order = prepare(GameId::Starfield, &tmp_dir.path());
1✔
1074

1✔
1075
        let full = prepare_bulk_full_plugins(&mut load_order);
1✔
1076

1✔
1077
        let mut plugin_refs = vec!["Starfield.esm"];
1✔
1078
        plugin_refs.extend(full[..255].iter().map(String::as_str));
1✔
1079

1✔
1080
        assert!(set_active_plugins(&mut load_order, &plugin_refs).is_err());
1✔
1081
        assert_eq!(1, load_order.active_plugin_names().len());
1✔
1082
    }
1✔
1083

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

1✔
1089
        let medium = prepare_bulk_medium_plugins(&mut load_order);
1✔
1090

1✔
1091
        let mut plugin_refs = vec!["Starfield.esm"];
1✔
1092
        plugin_refs.extend(medium[..257].iter().map(String::as_str));
1✔
1093

1✔
1094
        assert!(set_active_plugins(&mut load_order, &plugin_refs).is_err());
1✔
1095
        assert_eq!(1, load_order.active_plugin_names().len());
1✔
1096
    }
1✔
1097

1098
    #[test]
1099
    fn set_active_plugins_should_error_if_given_more_than_4096_light_plugins() {
1✔
1100
        let tmp_dir = tempdir().unwrap();
1✔
1101
        let mut load_order = prepare(GameId::Starfield, &tmp_dir.path());
1✔
1102

1✔
1103
        let light = prepare_bulk_light_plugins(&mut load_order);
1✔
1104

1✔
1105
        let mut plugin_refs = vec!["Starfield.esm"];
1✔
1106
        plugin_refs.extend(light[..4097].iter().map(String::as_str));
1✔
1107

1✔
1108
        assert!(set_active_plugins(&mut load_order, &plugin_refs).is_err());
1✔
1109
        assert_eq!(1, load_order.active_plugin_names().len());
1✔
1110
    }
1✔
1111
}
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