blob: 2eda194cac9f3f68efd8fc1ab99c8dee6eea14da [file]
/*
* Copyright (C) 2025 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
mod build_flags;
mod sdk_version;
#[cfg(test)]
mod tests {
use crate::build_flags::{ReleaseConfigs, FLAGS_WE_CARE_ABOUT};
use crate::sdk_version::SdkVersion;
use regex::Regex;
use std::sync::LazyLock;
// the subset of build flags relevant for SDK finalization
static RELEASE_CONFIGS: LazyLock<ReleaseConfigs> = LazyLock::new(ReleaseConfigs::init);
fn sdk_version(release_config: &str) -> SdkVersion {
// use SDK_INT_FULL if set, otherwise fall back to SDK_INT
let s = &RELEASE_CONFIGS.flags[release_config]["RELEASE_PLATFORM_SDK_VERSION_FULL"];
if !s.is_empty() {
s.parse::<SdkVersion>().unwrap_or_else(|_| {
panic!(
"failed to parse RELEASE_PLATFORM_SDK_VERSION_FULL for {release_config} ({s}) as SdkVersion"
)
})
} else {
let s = &RELEASE_CONFIGS.flags[release_config]["RELEASE_PLATFORM_SDK_VERSION"];
s.parse::<SdkVersion>().unwrap_or_else(|_| {
panic!(
"failed to parse RELEASE_PLATFORM_SDK_VERSION for {release_config} ({s}) as SdkVersion"
)
})
}
}
#[test]
fn test_build_flags_in_trunk_and_trunk_staging_are_equal() {
// invariant: the values of the flags (that this test cares about) in RELEASE_CONFIGS.flags are equal
// across trunk and trunk_staging release configs
//
// this means that the rest of the tests can focus on trunk and ignore trunk_staging
for flag in FLAGS_WE_CARE_ABOUT {
assert_eq!(
RELEASE_CONFIGS.flags["trunk"][flag], RELEASE_CONFIGS.flags["trunk_staging"][flag],
"flag {flag} differenct across trunk and trunk_staging",
);
}
}
#[test]
fn test_trunk_is_never_rel() {
// invariant: the codename in trunk is never REL: trunk is always bleeding edge and thus
// always something later than the latest finalized (REL) platform
assert_ne!(RELEASE_CONFIGS.flags["trunk"]["RELEASE_PLATFORM_VERSION_CODENAME"], "REL");
}
#[test]
fn test_version_parity_if_next_is_not_rel() {
// invariant: the version code of trunk and next are identical, unless next is REL: then
// the version in trunk can be one less than the version in next (during the intermediate
// state where next is REL but we haven't created prebuilts/sdk/<new-version> yet), or the
// version in trunk is identical to the one in next
let next = &RELEASE_CONFIGS.aliases["next"];
if RELEASE_CONFIGS.flags[next]["RELEASE_PLATFORM_VERSION_CODENAME"] != "REL" {
// expect the versions to be identical
assert_eq!(
RELEASE_CONFIGS.flags[next]["RELEASE_PLATFORM_SDK_VERSION_FULL"],
RELEASE_CONFIGS.flags["trunk"]["RELEASE_PLATFORM_SDK_VERSION_FULL"]
);
} else {
// make sure the version in trunk is less or equal to that of next
//
// ideally this should check that trunk is at most one version behind next, but we
// can't tell what that means, so let's settle for the weaker guarantee of "less or
// equal"
assert!(sdk_version("trunk") <= sdk_version(next));
}
}
#[test]
fn test_version_and_version_full_parity() {
// invariant: for the release configs that set RELEASE_PLATFORM_SDK_VERSION_FULL:
// - the value can be parsed as a float
// - the value contains a decimal separator
// - the value before the decimal separator is identical to RELEASE_PLATFORM_SDK_VERSION
// (e.g. 36.0 and 36)
for release_config in RELEASE_CONFIGS.flags.keys() {
let sdk_int_full =
&RELEASE_CONFIGS.flags[release_config]["RELEASE_PLATFORM_SDK_VERSION_FULL"];
if sdk_int_full.is_empty() {
// skip this release config if it doesn't set RELEASE_PLATFORM_SDK_VERSION_FULL
continue;
}
assert!(
sdk_int_full.parse::<f32>().is_ok(),
"failed to convert value ({sdk_int_full}) of RELEASE_PLATFORM_SDK_VERSION_FULL for {release_config} to f32"
);
let (integer_part, _) = sdk_int_full.split_once(".").unwrap_or_else(|| panic!("value of RELEASE_PLATFORM_SDK_VERSION_FULL ({sdk_int_full}) for {release_config} doesn't have expected format"));
let sdk_int = &RELEASE_CONFIGS.flags[release_config]["RELEASE_PLATFORM_SDK_VERSION"];
assert_eq!(
integer_part, sdk_int,
"in release config {release_config}, expected parity between the integer part ({integer_part}) of RELEASE_PLATFORM_SDK_VERSION_FULL ({sdk_int_full}) and RELEASE_PLATFORM_SDK_VERSION ({sdk_int})"
);
}
}
#[test]
fn test_release_hidden_api_exportable_stubs_is_enabled_in_next() {
// invariant: RELEASE_HIDDEN_API_EXPORTABLE_STUBS is set to `true` in `next`, because we'll
// cut an Android release from this release config (the flag is too expensive in terms of
// build performance to enable everywhere)
let next = &RELEASE_CONFIGS.aliases["next"];
let value = &RELEASE_CONFIGS.flags[next]["RELEASE_HIDDEN_API_EXPORTABLE_STUBS"];
assert_eq!(
value, "true",
"expected RELEASE_HIDDEN_API_EXPORTABLE_STUBS to be 'true' in next ({next}) but was '{value}'"
);
}
#[test]
fn test_only_canary_release_config_has_codename_canary() {
// invariant: only the canary release config ("canary", aliased to "zp11") sets codename to
// CANARY; no release config inherits from the canary release config
let canary = &RELEASE_CONFIGS.aliases["canary"];
let value = &RELEASE_CONFIGS.flags[canary]["RELEASE_PLATFORM_VERSION_CODENAME"];
assert_eq!(value, "CANARY");
for release_config in RELEASE_CONFIGS.flags.keys().filter(|key| *key != canary) {
let value = &RELEASE_CONFIGS.flags[release_config]["RELEASE_PLATFORM_VERSION_CODENAME"];
assert_ne!(value, "CANARY");
}
}
#[test]
fn test_prospective_sdk_version() {
// invariant: if set, RELEASE_PROSPECTIVE_SDK_VERSION_FULL is greater or equal to the
// current SDK version (we never re-finalize older SDK versions)
for release_config in RELEASE_CONFIGS.flags.keys() {
let prospective_version = &RELEASE_CONFIGS.flags[release_config]
["RELEASE_PLATFORM_PROSPECTIVE_SDK_VERSION_FULL"];
if !prospective_version.is_empty() {
let prospective_version = prospective_version.parse::<SdkVersion>().unwrap();
let sdk_version = sdk_version(release_config);
assert!(prospective_version >= sdk_version, "in release config {release_config}, expected RELEASE_PROSPECTIVE_SDK_VERSION_FULL ({prospective_version}) to not be set, or to be greater or equal to the SDK version ({sdk_version})");
}
}
}
#[test]
fn test_preview_sdk_int() {
// invariants for the value of RELEASE_PLATFORM_PREVIEW_SDK_INT:
// - codename is CANARY: ${YYYY}${MM}${DD}
// - if codename is REL and this is not a beta release: 0
// - if codename is REL and this is a beta release: ${MM}${m}${b}
// (four chars, MM: major version, m: minor version, b: beta release number)
// - if codename is not REL: 1
let re_release_config_beta = Regex::new(r"^.p.\d$").unwrap();
let re_preview_sdk_int_canary = Regex::new(r"^(\d{4})(\d{2})(\d{2})$").unwrap();
let re_preview_sdk_int_beta = Regex::new(r"^(\d{2})(\d)\d$").unwrap();
for release_config in RELEASE_CONFIGS.flags.keys() {
let codename =
&RELEASE_CONFIGS.flags[release_config]["RELEASE_PLATFORM_VERSION_CODENAME"];
let preview_sdk_int =
&RELEASE_CONFIGS.flags[release_config]["RELEASE_PLATFORM_PREVIEW_SDK_INT"];
if codename == "CANARY" {
let error_msg = format!("in release config {release_config}, expected RELEASE_PLATFORM_PREVIEW_SDK_INT to be ${{YYYY}}${{MM}}${{DD}} but was {preview_sdk_int}");
let Some(caps) = re_preview_sdk_int_canary.captures(preview_sdk_int) else {
panic!("{error_msg}");
};
let year = caps[1].parse::<u32>().unwrap();
let month = caps[2].parse::<u32>().unwrap();
let day = caps[3].parse::<u32>().unwrap();
assert!((2026..=3000).contains(&year), "{error_msg}: unreasonable year");
assert!((1..=12).contains(&month), "{error_msg}: unreasonable month");
// not all months have 31 days, but this is good enough
assert!((1..=31).contains(&day), "{error_msg}: unreasonable day");
} else if codename == "REL" {
let preview_sdk_int = preview_sdk_int.parse::<u32>().unwrap();
assert_eq!(preview_sdk_int, 0, "in release config {release_config}, expected RELEASE_PLATFORM_PREVIEW_SDK_INT to be 0 but was {preview_sdk_int}");
} else if re_release_config_beta.is_match(release_config) {
let error_msg = format!("in release config {release_config}, expected RELEASE_PLATFORM_PREVIEW_SDK_INT to be ${{MM}}${{m}}${{b}} but was {preview_sdk_int}");
let Some(caps) = re_preview_sdk_int_beta.captures(preview_sdk_int) else {
panic!("{error_msg}");
};
let beta = SdkVersion {
major: caps[1].parse::<u32>().unwrap(),
minor: caps[2].parse::<u32>().unwrap(),
};
let current = sdk_version(release_config);
assert!((beta.major > current.major && beta.minor == 0) || (beta.major == current.major && beta.minor > current.minor), "in release config {release_config}, expected the release encoded in RELEASE_PLATFORM_PREVIEW_SDK_INT ({beta}) to encode a later release than RELEASE_PLATFORM_SDK_VERSION_FULL ({beta})");
} else {
let preview_sdk_int = preview_sdk_int.parse::<u32>().unwrap();
assert_ne!(preview_sdk_int, 0, "in release config {release_config}, expected RELEASE_PLATFORM_PREVIEW_SDK_INT to be non-zero but was 0");
}
}
}
}