lux_lib/lua_rockspec/
platform.rs

1use itertools::Itertools;
2use mlua::{FromLua, IntoLuaMulti, Lua, LuaSerdeExt, UserData, Value};
3use std::{cmp::Ordering, collections::HashMap, marker::PhantomData};
4use strum::IntoEnumIterator;
5use strum_macros::EnumIter;
6use thiserror::Error;
7
8use serde::{
9    de::{self, DeserializeOwned},
10    Deserialize, Deserializer,
11};
12use serde_enum_str::{Deserialize_enum_str, Serialize_enum_str};
13
14use super::{DisplayAsLuaKV, DisplayLuaKV, DisplayLuaValue};
15
16/// Identifier by a platform.
17/// The `PartialOrd` instance views more specific platforms as `Greater`
18#[derive(Deserialize_enum_str, Serialize_enum_str, PartialEq, Eq, Hash, Debug, Clone, EnumIter)]
19#[serde(rename_all = "lowercase")]
20#[strum(serialize_all = "lowercase")]
21pub enum PlatformIdentifier {
22    // TODO: Add undocumented platform identifiers from luarocks codebase?
23    Unix,
24    Windows,
25    Win32,
26    Cygwin,
27    MacOSX,
28    Linux,
29    FreeBSD,
30    #[serde(other)]
31    Unknown(String),
32}
33
34impl Default for PlatformIdentifier {
35    fn default() -> Self {
36        target_identifier()
37    }
38}
39
40// Order by specificity -> less specific = `Less`
41impl PartialOrd for PlatformIdentifier {
42    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
43        match (self, other) {
44            (PlatformIdentifier::Unix, PlatformIdentifier::Cygwin) => Some(Ordering::Less),
45            (PlatformIdentifier::Unix, PlatformIdentifier::MacOSX) => Some(Ordering::Less),
46            (PlatformIdentifier::Unix, PlatformIdentifier::Linux) => Some(Ordering::Less),
47            (PlatformIdentifier::Unix, PlatformIdentifier::FreeBSD) => Some(Ordering::Less),
48            (PlatformIdentifier::Windows, PlatformIdentifier::Win32) => Some(Ordering::Greater),
49            (PlatformIdentifier::Win32, PlatformIdentifier::Windows) => Some(Ordering::Less),
50            (PlatformIdentifier::Cygwin, PlatformIdentifier::Unix) => Some(Ordering::Greater),
51            (PlatformIdentifier::MacOSX, PlatformIdentifier::Unix) => Some(Ordering::Greater),
52            (PlatformIdentifier::Linux, PlatformIdentifier::Unix) => Some(Ordering::Greater),
53            (PlatformIdentifier::FreeBSD, PlatformIdentifier::Unix) => Some(Ordering::Greater),
54            _ if self == other => Some(Ordering::Equal),
55            _ => None,
56        }
57    }
58}
59
60impl FromLua for PlatformIdentifier {
61    fn from_lua(value: Value, lua: &Lua) -> mlua::Result<Self> {
62        let string = String::from_lua(value, lua)?;
63        Ok(string
64            .parse()
65            .unwrap_or(PlatformIdentifier::Unknown(string)))
66    }
67}
68
69/// Retrieves the platform identifier for the target platform
70///
71/// NOTE: This is the platform lux was built with.
72/// As we don't support cross-compilation, we currently expect
73/// users to use a version of lux that was built with the same platform
74/// as the one they are targeting
75fn target_identifier() -> PlatformIdentifier {
76    if cfg!(target_env = "msvc") {
77        PlatformIdentifier::Windows
78    } else if cfg!(target_os = "linux") {
79        PlatformIdentifier::Linux
80    } else if cfg!(target_os = "macos") || cfg!(target_vendor = "apple") {
81        PlatformIdentifier::MacOSX
82    } else if cfg!(target_os = "freebsd") {
83        PlatformIdentifier::FreeBSD
84    } else if which::which("cygpath").is_ok() {
85        PlatformIdentifier::Cygwin
86    } else {
87        PlatformIdentifier::Unix
88    }
89}
90
91impl PlatformIdentifier {
92    /// Get identifiers that are a subset of this identifier.
93    /// For example, Unix is a subset of Linux
94    pub fn get_subsets(&self) -> Vec<Self> {
95        PlatformIdentifier::iter()
96            .filter(|identifier| identifier.is_subset_of(self))
97            .collect()
98    }
99
100    /// Get identifiers that are an extension of this identifier.
101    /// For example, Linux is an extension of Unix
102    pub fn get_extended_platforms(&self) -> Vec<Self> {
103        PlatformIdentifier::iter()
104            .filter(|identifier| identifier.is_extension_of(self))
105            .collect()
106    }
107
108    /// e.g. Unix is a subset of Linux
109    fn is_subset_of(&self, other: &PlatformIdentifier) -> bool {
110        self.partial_cmp(other) == Some(Ordering::Less)
111    }
112
113    /// e.g. Linux is an extension of Unix
114    fn is_extension_of(&self, other: &PlatformIdentifier) -> bool {
115        self.partial_cmp(other) == Some(Ordering::Greater)
116    }
117}
118
119#[derive(Clone, Debug, PartialEq)]
120pub struct PlatformSupport {
121    /// Do not match this platform
122    platform_map: HashMap<PlatformIdentifier, bool>,
123}
124
125impl Default for PlatformSupport {
126    fn default() -> Self {
127        Self {
128            platform_map: PlatformIdentifier::iter()
129                .filter(|identifier| !matches!(identifier, PlatformIdentifier::Unknown(_)))
130                .map(|identifier| (identifier, true))
131                .collect(),
132        }
133    }
134}
135
136impl UserData for PlatformSupport {
137    fn add_methods<M: mlua::UserDataMethods<Self>>(methods: &mut M) {
138        methods.add_method("is_supported", |_, this, platform: PlatformIdentifier| {
139            Ok(this.is_supported(&platform))
140        });
141    }
142}
143
144impl<'de> Deserialize<'de> for PlatformSupport {
145    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
146    where
147        D: Deserializer<'de>,
148    {
149        let platforms: Vec<String> = Vec::deserialize(deserializer)?;
150        Self::parse(&platforms).map_err(de::Error::custom)
151    }
152}
153
154impl DisplayAsLuaKV for PlatformSupport {
155    fn display_lua(&self) -> DisplayLuaKV {
156        DisplayLuaKV {
157            key: "supported_platforms".to_string(),
158            value: DisplayLuaValue::List(
159                self.platforms()
160                    .iter()
161                    .map(|(platform, supported)| {
162                        DisplayLuaValue::String(format!(
163                            "{}{}",
164                            if *supported { "" } else { "!" },
165                            platform,
166                        ))
167                    })
168                    .collect(),
169            ),
170        }
171    }
172}
173
174#[derive(Error, Debug)]
175pub enum PlatformValidationError {
176    #[error("error when parsing platform identifier: {0}")]
177    ParseError(String),
178
179    #[error("conflicting supported platform entries")]
180    ConflictingEntries,
181}
182
183impl PlatformSupport {
184    fn validate_platforms(
185        platforms: &[String],
186    ) -> Result<HashMap<PlatformIdentifier, bool>, PlatformValidationError> {
187        platforms
188            .iter()
189            .try_fold(HashMap::new(), |mut platforms, platform| {
190                // Platform assertions can exist in one of the following forms:
191                // - `platform` - a positive assertion for the platform (the platform must be present)
192                // - `!platform` - a negative assertion for the platform (any platform *but* this one must be present)
193                let (is_positive_assertion, platform) = platform
194                    .strip_prefix('!')
195                    .map(|str| (false, str))
196                    .unwrap_or((true, platform));
197
198                let platform_identifier = platform
199                    .parse::<PlatformIdentifier>()
200                    .map_err(|err| PlatformValidationError::ParseError(err.to_string()))?;
201
202                // If a platform with the same name exists already and is contradictory
203                // then throw an error. An example of such a contradiction is e.g.:
204                // [`win32`, `!win32`]
205                if platforms
206                    .get(&platform_identifier)
207                    .unwrap_or(&is_positive_assertion)
208                    != &is_positive_assertion
209                {
210                    return Err(PlatformValidationError::ConflictingEntries);
211                }
212
213                platforms.insert(platform_identifier.clone(), is_positive_assertion);
214
215                let subset_or_extended_platforms = if is_positive_assertion {
216                    platform_identifier.get_extended_platforms()
217                } else {
218                    platform_identifier.get_subsets()
219                };
220
221                for sub_platform in subset_or_extended_platforms {
222                    if platforms
223                        .get(&sub_platform)
224                        .unwrap_or(&is_positive_assertion)
225                        != &is_positive_assertion
226                    {
227                        // TODO(vhyrro): More detailed errors
228                        return Err(PlatformValidationError::ConflictingEntries);
229                    }
230
231                    platforms.insert(sub_platform, is_positive_assertion);
232                }
233
234                Ok(platforms)
235            })
236    }
237
238    pub fn parse(platforms: &[String]) -> Result<Self, PlatformValidationError> {
239        // Platforms are matched in one of two ways: exclusively or inclusively.
240        // If only positive matches are present, then the platforms are matched inclusively (as you only support the matches that you specified).
241        // If any negative matches are present, then the platforms are matched exclusively (as you want to support any operating system *other* than the ones you negated).
242        match platforms {
243            [] => Ok(Self::default()),
244            platforms if platforms.iter().any(|platform| platform.starts_with('!')) => {
245                let mut platform_map = Self::validate_platforms(platforms)?;
246
247                // Loop through all identifiers and set them to true if they are not present in
248                // the map (exclusive matching).
249                for identifier in PlatformIdentifier::iter() {
250                    if !matches!(identifier, PlatformIdentifier::Unknown(_)) {
251                        platform_map.entry(identifier).or_insert(true);
252                    }
253                }
254
255                Ok(Self { platform_map })
256            }
257            // Only validate positive matches (inclusive matching)
258            platforms => Ok(Self {
259                platform_map: Self::validate_platforms(platforms)?,
260            }),
261        }
262    }
263
264    pub fn is_supported(&self, platform: &PlatformIdentifier) -> bool {
265        self.platform_map.get(platform).cloned().unwrap_or(false)
266    }
267
268    pub(crate) fn platforms(&self) -> &HashMap<PlatformIdentifier, bool> {
269        &self.platform_map
270    }
271}
272
273pub trait PartialOverride: Sized {
274    type Err: std::error::Error;
275
276    fn apply_overrides(&self, override_val: &Self) -> Result<Self, Self::Err>;
277}
278
279pub trait PlatformOverridable: PartialOverride {
280    type Err: std::error::Error;
281
282    fn on_nil<T>() -> Result<PerPlatform<T>, <Self as PlatformOverridable>::Err>
283    where
284        T: PlatformOverridable,
285        T: Default;
286}
287
288pub trait FromPlatformOverridable<T: PlatformOverridable, G: FromPlatformOverridable<T, G>> {
289    type Err: std::error::Error;
290
291    fn from_platform_overridable(internal: T) -> Result<G, Self::Err>;
292}
293
294/// Data that that can vary per platform
295#[derive(Clone, Debug, PartialEq)]
296pub struct PerPlatform<T> {
297    /// The base data, applicable if no platform is specified
298    pub(crate) default: T,
299    /// The per-platform override, if present.
300    pub(crate) per_platform: HashMap<PlatformIdentifier, T>,
301}
302
303impl<T> PerPlatform<T> {
304    pub(crate) fn new(default: T) -> Self {
305        Self {
306            default,
307            per_platform: HashMap::default(),
308        }
309    }
310
311    /// Merge per-platform overrides for the configured build target platform,
312    /// with more specific platform overrides having higher priority.
313    pub fn current_platform(&self) -> &T {
314        self.for_platform_identifier(&target_identifier())
315    }
316
317    fn for_platform_identifier(&self, identifier: &PlatformIdentifier) -> &T {
318        self.get(identifier)
319    }
320
321    pub fn get(&self, platform: &PlatformIdentifier) -> &T {
322        self.per_platform.get(platform).unwrap_or(
323            platform
324                .get_subsets()
325                .into_iter()
326                // More specific platforms first.
327                // This is safe because a platform's subsets
328                // can be totally ordered among each other.
329                .sorted_by(|a, b| b.partial_cmp(a).unwrap_or(Ordering::Equal))
330                .find(|identifier| self.per_platform.contains_key(identifier))
331                .and_then(|identifier| self.per_platform.get(&identifier))
332                .unwrap_or(&self.default),
333        )
334    }
335
336    pub(crate) fn map<U, F>(&self, cb: F) -> PerPlatform<U>
337    where
338        F: Fn(&T) -> U,
339    {
340        PerPlatform {
341            default: cb(&self.default),
342            per_platform: self
343                .per_platform
344                .iter()
345                .map(|(identifier, value)| (identifier.clone(), cb(value)))
346                .collect(),
347        }
348    }
349}
350
351impl<U, E> PerPlatform<Result<U, E>>
352where
353    E: std::error::Error,
354{
355    pub fn transpose(self) -> Result<PerPlatform<U>, E> {
356        Ok(PerPlatform {
357            default: self.default?,
358            per_platform: self
359                .per_platform
360                .into_iter()
361                .map(|(identifier, value)| Ok((identifier, value?)))
362                .try_collect()?,
363        })
364    }
365}
366
367impl<T: Default> Default for PerPlatform<T> {
368    fn default() -> Self {
369        Self {
370            default: T::default(),
371            per_platform: HashMap::default(),
372        }
373    }
374}
375
376impl<'de, T> Deserialize<'de> for PerPlatform<T>
377where
378    T: Deserialize<'de>,
379    T: Clone,
380    T: PartialOverride,
381{
382    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
383    where
384        D: Deserializer<'de>,
385    {
386        let mut map = toml::map::Map::deserialize(deserializer)?;
387
388        let mut per_platform: HashMap<PlatformIdentifier, T> = map
389            .remove("platforms")
390            .map_or(Ok(HashMap::default()), |platforms| platforms.try_into())
391            .map_err(serde::de::Error::custom)?;
392
393        let default: T = map.try_into().map_err(serde::de::Error::custom)?;
394
395        apply_per_platform_overrides(&mut per_platform, &default)
396            .map_err(serde::de::Error::custom)?;
397
398        Ok(PerPlatform {
399            default,
400            per_platform,
401        })
402    }
403}
404
405impl<T> FromLua for PerPlatform<T>
406where
407    T: PlatformOverridable,
408    T: PartialOverride,
409    T: DeserializeOwned,
410    T: Default,
411    T: Clone,
412{
413    fn from_lua(value: Value, lua: &Lua) -> mlua::Result<Self> {
414        match &value {
415            list @ Value::Table(tbl) => {
416                let mut per_platform = match tbl.get("platforms")? {
417                    val @ Value::Table(_) => Ok(lua.from_value(val)?),
418                    Value::Nil => Ok(HashMap::default()),
419                    val => Err(mlua::Error::DeserializeError(format!(
420                        "Expected platforms to be a table or nil, but got {}",
421                        val.type_name()
422                    ))),
423                }?;
424                let _ = tbl.raw_remove("platforms");
425                let default = lua.from_value(list.to_owned())?;
426                apply_per_platform_overrides(&mut per_platform, &default).map_err(
427                    |err: <T as PartialOverride>::Err| {
428                        mlua::Error::DeserializeError(err.to_string())
429                    },
430                )?;
431                Ok(PerPlatform {
432                    default,
433                    per_platform,
434                })
435            }
436            Value::Nil => T::on_nil().map_err(|err| mlua::Error::DeserializeError(err.to_string())),
437            val => Err(mlua::Error::DeserializeError(format!(
438                "Expected rockspec external dependencies to be a table or nil, but got {}",
439                val.type_name()
440            ))),
441        }
442    }
443}
444
445impl<T> UserData for PerPlatform<T>
446where
447    T: IntoLuaMulti + Clone,
448{
449    fn add_methods<M: mlua::UserDataMethods<Self>>(methods: &mut M) {
450        // TODO(mrcjkb): YAGNI?
451        // methods.add_method("current_platform", |_, this, _: ()| {
452        //     Ok(this.for_target_platform().clone())
453        // });
454        methods.add_method("get", |_, this, platform: PlatformIdentifier| {
455            Ok(this.get(&platform).clone())
456        });
457    }
458}
459
460/// Newtype wrapper used to implement a `FromLua` instance for `FromPlatformOverridable`
461/// This is necessary, because Rust doesn't yet support specialization.
462pub struct PerPlatformWrapper<T, G> {
463    pub un_per_platform: PerPlatform<T>,
464    phantom: PhantomData<G>,
465}
466
467impl<T, G> FromLua for PerPlatformWrapper<T, G>
468where
469    T: FromPlatformOverridable<G, T, Err: ToString>,
470    G: PlatformOverridable<Err: ToString>,
471    G: DeserializeOwned,
472    G: Default,
473    G: Clone,
474{
475    fn from_lua(value: Value, lua: &Lua) -> mlua::Result<Self> {
476        let internal = PerPlatform::from_lua(value, lua)?;
477        let per_platform: HashMap<_, _> = internal
478            .per_platform
479            .into_iter()
480            .map(|(platform, internal_override)| {
481                let override_spec = T::from_platform_overridable(internal_override)
482                    .map_err(|err| mlua::Error::DeserializeError(err.to_string()))?;
483
484                Ok((platform, override_spec))
485            })
486            .try_collect::<_, _, mlua::Error>()?;
487        let un_per_platform = PerPlatform {
488            default: T::from_platform_overridable(internal.default)
489                .map_err(|err| mlua::Error::DeserializeError(err.to_string()))?,
490            per_platform,
491        };
492        Ok(PerPlatformWrapper {
493            un_per_platform,
494            phantom: PhantomData,
495        })
496    }
497}
498
499impl<'de, T, G> Deserialize<'de> for PerPlatformWrapper<T, G>
500where
501    T: FromPlatformOverridable<G, T, Err: ToString>,
502    G: PlatformOverridable<Err: ToString>,
503    G: DeserializeOwned,
504    G: Default,
505    G: Clone,
506{
507    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
508    where
509        D: Deserializer<'de>,
510    {
511        let internal = PerPlatform::deserialize(deserializer)?;
512        let per_platform: HashMap<_, _> = internal
513            .per_platform
514            .into_iter()
515            .map(|(platform, internal_override)| {
516                let override_spec = T::from_platform_overridable(internal_override)
517                    .map_err(serde::de::Error::custom)?;
518
519                Ok((platform, override_spec))
520            })
521            .try_collect::<_, _, D::Error>()?;
522        let un_per_platform = PerPlatform {
523            default: T::from_platform_overridable(internal.default)
524                .map_err(serde::de::Error::custom)?,
525            per_platform,
526        };
527        Ok(PerPlatformWrapper {
528            un_per_platform,
529            phantom: PhantomData,
530        })
531    }
532}
533
534fn apply_per_platform_overrides<T>(
535    per_platform: &mut HashMap<PlatformIdentifier, T>,
536    base: &T,
537) -> Result<(), T::Err>
538where
539    T: PartialOverride,
540    T: Clone,
541{
542    let per_platform_raw = per_platform.clone();
543    for (platform, overrides) in per_platform.clone() {
544        // Add base values for each platform
545        let overridden = base.apply_overrides(&overrides)?;
546        per_platform.insert(platform, overridden);
547    }
548    for (platform, overrides) in per_platform_raw {
549        // Add extended platform dependencies (without base deps) for each platform
550        for extended_platform in &platform.get_extended_platforms() {
551            if let Some(extended_overrides) = per_platform.get(extended_platform) {
552                per_platform.insert(
553                    extended_platform.to_owned(),
554                    extended_overrides.apply_overrides(&overrides)?,
555                );
556            }
557        }
558    }
559    Ok(())
560}
561
562#[cfg(test)]
563mod tests {
564
565    use super::*;
566    use proptest::prelude::*;
567
568    fn platform_identifier_strategy() -> impl Strategy<Value = PlatformIdentifier> {
569        prop_oneof![
570            Just(PlatformIdentifier::Unix),
571            Just(PlatformIdentifier::Windows),
572            Just(PlatformIdentifier::Win32),
573            Just(PlatformIdentifier::Cygwin),
574            Just(PlatformIdentifier::MacOSX),
575            Just(PlatformIdentifier::Linux),
576            Just(PlatformIdentifier::FreeBSD),
577        ]
578    }
579
580    #[tokio::test]
581    async fn sort_platform_identifier_more_specific_last() {
582        let mut platforms = vec![
583            PlatformIdentifier::Cygwin,
584            PlatformIdentifier::Linux,
585            PlatformIdentifier::Unix,
586        ];
587        platforms.sort_by(|a, b| a.partial_cmp(b).unwrap_or(Ordering::Equal));
588        assert_eq!(
589            platforms,
590            vec![
591                PlatformIdentifier::Unix,
592                PlatformIdentifier::Cygwin,
593                PlatformIdentifier::Linux
594            ]
595        );
596        let mut platforms = vec![PlatformIdentifier::Windows, PlatformIdentifier::Win32];
597        platforms.sort_by(|a, b| a.partial_cmp(b).unwrap_or(Ordering::Equal));
598        assert_eq!(
599            platforms,
600            vec![PlatformIdentifier::Win32, PlatformIdentifier::Windows]
601        )
602    }
603
604    #[tokio::test]
605    async fn test_is_subset_of() {
606        assert!(PlatformIdentifier::Unix.is_subset_of(&PlatformIdentifier::Linux));
607        assert!(PlatformIdentifier::Unix.is_subset_of(&PlatformIdentifier::MacOSX));
608        assert!(!PlatformIdentifier::Linux.is_subset_of(&PlatformIdentifier::Unix));
609    }
610
611    #[tokio::test]
612    async fn test_is_extension_of() {
613        assert!(PlatformIdentifier::Linux.is_extension_of(&PlatformIdentifier::Unix));
614        assert!(PlatformIdentifier::MacOSX.is_extension_of(&PlatformIdentifier::Unix));
615        assert!(!PlatformIdentifier::Unix.is_extension_of(&PlatformIdentifier::Linux));
616    }
617
618    #[tokio::test]
619    async fn per_platform() {
620        let foo = PerPlatform {
621            default: "default",
622            per_platform: vec![
623                (PlatformIdentifier::Unix, "unix"),
624                (PlatformIdentifier::FreeBSD, "freebsd"),
625                (PlatformIdentifier::Cygwin, "cygwin"),
626                (PlatformIdentifier::Linux, "linux"),
627            ]
628            .into_iter()
629            .collect(),
630        };
631        assert_eq!(*foo.get(&PlatformIdentifier::MacOSX), "unix");
632        assert_eq!(*foo.get(&PlatformIdentifier::Linux), "linux");
633        assert_eq!(*foo.get(&PlatformIdentifier::FreeBSD), "freebsd");
634        assert_eq!(*foo.get(&PlatformIdentifier::Cygwin), "cygwin");
635        assert_eq!(*foo.get(&PlatformIdentifier::Windows), "default");
636    }
637
638    #[cfg(target_os = "linux")]
639    #[tokio::test]
640    async fn test_target_identifier() {
641        run_test_target_identifier(PlatformIdentifier::Linux)
642    }
643
644    #[cfg(target_os = "macos")]
645    #[tokio::test]
646    async fn test_target_identifier() {
647        run_test_target_identifier(PlatformIdentifier::MacOSX)
648    }
649
650    #[cfg(target_env = "msvc")]
651    #[tokio::test]
652    async fn test_target_identifier() {
653        run_test_target_identifier(PlatformIdentifier::Windows)
654    }
655
656    fn run_test_target_identifier(expected: PlatformIdentifier) {
657        assert_eq!(expected, target_identifier());
658    }
659
660    proptest! {
661        #[test]
662        fn supported_platforms(identifier in platform_identifier_strategy()) {
663            let identifier_str = identifier.to_string();
664            let platforms = vec![identifier_str];
665            let platform_support = PlatformSupport::parse(&platforms).unwrap();
666            prop_assert!(platform_support.is_supported(&identifier))
667        }
668
669        #[test]
670        fn unsupported_platforms_only(unsupported in platform_identifier_strategy(), supported in platform_identifier_strategy()) {
671            if supported == unsupported
672                || unsupported.is_extension_of(&supported) {
673                return Ok(());
674            }
675            let identifier_str = format!("!{}", unsupported);
676            let platforms = vec![identifier_str];
677            let platform_support = PlatformSupport::parse(&platforms).unwrap();
678            prop_assert!(!platform_support.is_supported(&unsupported));
679            prop_assert!(platform_support.is_supported(&supported))
680        }
681
682        #[test]
683        fn supported_and_unsupported_platforms(unsupported in platform_identifier_strategy(), unspecified in platform_identifier_strategy()) {
684            if unspecified == unsupported
685                || unsupported.is_extension_of(&unspecified) {
686                return Ok(());
687            }
688            let supported_str = unspecified.to_string();
689            let unsupported_str = format!("!{}", unsupported);
690            let platforms = vec![supported_str, unsupported_str];
691            let platform_support = PlatformSupport::parse(&platforms).unwrap();
692            prop_assert!(platform_support.is_supported(&unspecified));
693            prop_assert!(!platform_support.is_supported(&unsupported));
694        }
695
696        #[test]
697        fn all_platforms_supported_if_none_are_specified(identifier in platform_identifier_strategy()) {
698            let platforms = vec![];
699            let platform_support = PlatformSupport::parse(&platforms).unwrap();
700            prop_assert!(platform_support.is_supported(&identifier))
701        }
702
703        #[test]
704        fn conflicting_platforms(identifier in platform_identifier_strategy()) {
705            let identifier_str = identifier.to_string();
706            let identifier_str_negated = format!("!{}", identifier);
707            let platforms = vec![identifier_str, identifier_str_negated];
708            let _ = PlatformSupport::parse(&platforms).unwrap_err();
709        }
710
711        #[test]
712        fn extended_platforms_supported_if_supported(identifier in platform_identifier_strategy()) {
713            let identifier_str = identifier.to_string();
714            let platforms = vec![identifier_str];
715            let platform_support = PlatformSupport::parse(&platforms).unwrap();
716            for identifier in identifier.get_extended_platforms() {
717                prop_assert!(platform_support.is_supported(&identifier))
718            }
719        }
720
721        #[test]
722        fn sub_platforms_unsupported_if_unsupported(identifier in platform_identifier_strategy()) {
723            let identifier_str = format!("!{}", identifier);
724            let platforms = vec![identifier_str];
725            let platform_support = PlatformSupport::parse(&platforms).unwrap();
726            for identifier in identifier.get_subsets() {
727                prop_assert!(!platform_support.is_supported(&identifier))
728            }
729        }
730
731        #[test]
732        fn conflicting_extended_platform_definitions(identifier in platform_identifier_strategy()) {
733            let extended_platforms = identifier.get_extended_platforms();
734            if extended_platforms.is_empty() {
735                return Ok(());
736            }
737            let supported_str = identifier.to_string();
738            let mut platforms: Vec<String> = extended_platforms.into_iter().map(|ident| format!("!{}", ident)).collect();
739            platforms.push(supported_str);
740            let _ = PlatformSupport::parse(&platforms).unwrap_err();
741        }
742    }
743}