1use {
2 crate::{self as v3, compute_commit, ClientId},
3 rand::{thread_rng, Rng},
4 serde::{de::Error as _, ser::Error as _, Deserialize, Deserializer, Serialize, Serializer},
5 solana_sanitize::Sanitize,
6 solana_serde_varint as serde_varint,
7 std::{convert::TryInto, fmt, str::FromStr},
8};
9
10#[cfg_attr(feature = "frozen-abi", derive(AbiExample))]
11#[derive(Clone, Debug, PartialEq, Eq)]
12pub enum Prerelease {
13 Stable,
14 ReleaseCandidate(u16),
15 Beta(u16),
16 Alpha(u16),
17}
18
19impl Prerelease {
20 const ENCODE_TAG_STABLE: u16 = 0;
21 const ENCODE_TAG_RELEASE_CANDIDATE: u16 = 1;
22 const ENCODE_TAG_BETA: u16 = 2;
23 const ENCODE_TAG_ALPHA: u16 = 3;
24 const IDENTIFIER_RELEASE_CANDIDATE: &str = "rc";
25 const IDENTIFIER_BETA: &str = "beta";
26 const IDENTIFIER_ALPHA: &str = "alpha";
27
28 pub fn patch_is_valid(&self, patch: u16) -> bool {
29 *self == Self::Stable || patch == 0
30 }
31}
32
33#[derive(Clone, Debug, PartialEq)]
34pub enum ParsePrereleaseError {
35 DotSeparatorMissing,
36 NumericPartNotAU16,
37 UnknownIdentifier,
38}
39
40impl fmt::Display for Prerelease {
41 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
42 let (identifier, number) = match &self {
43 Self::Stable => return Ok(()),
44 Self::ReleaseCandidate(rc) => (Self::IDENTIFIER_RELEASE_CANDIDATE, rc),
45 Self::Beta(beta) => (Self::IDENTIFIER_BETA, beta),
46 Self::Alpha(alpha) => (Self::IDENTIFIER_ALPHA, alpha),
47 };
48 write!(f, "{identifier}.{number}")
49 }
50}
51
52impl FromStr for Prerelease {
53 type Err = ParsePrereleaseError;
54 fn from_str(s: &str) -> Result<Self, Self::Err> {
55 if s.is_empty() {
56 Ok(Self::Stable)
57 } else {
58 let mut parts = s.rsplitn(2, '.');
59 let num_part = parts
60 .next()
61 .expect("rsplitn returns at least one empty string");
62 let identifier = parts
63 .next()
64 .ok_or(ParsePrereleaseError::DotSeparatorMissing)?;
65 assert_eq!(
66 parts.next(),
67 None,
68 "Safety: rsplitn(2, ...) produces no more than two parts"
69 );
70 let num =
71 u16::from_str(num_part).map_err(|_| ParsePrereleaseError::NumericPartNotAU16)?;
72 match identifier {
73 Self::IDENTIFIER_RELEASE_CANDIDATE => Ok(Self::ReleaseCandidate(num)),
74 Self::IDENTIFIER_BETA => Ok(Self::Beta(num)),
75 Self::IDENTIFIER_ALPHA => Ok(Self::Alpha(num)),
76 _ => Err(ParsePrereleaseError::UnknownIdentifier),
77 }
78 }
79 }
80}
81
82#[cfg_attr(feature = "frozen-abi", derive(AbiExample))]
83#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
84#[serde(transparent)]
85struct PackedMinor(#[serde(with = "serde_varint")] u16);
86
87#[derive(Clone, Debug, PartialEq)]
88pub enum PackedMinorPackError {
89 MinorTooLarge,
90 InvalidPatchForPrerelease,
91}
92
93#[derive(Clone, Debug, PartialEq)]
94pub enum PackedMinorUnpackError {
95 ReservedBitsSet,
96}
97
98impl PackedMinor {
99 const PRERELEASE_BITS_OFFSET: u32 = 14;
100 const PRERELEASE_MASK_BITS: u32 = 2;
101 const PRERELEASE_FIRST_UNMASKED_BIT: u16 = 1 << Self::PRERELEASE_MASK_BITS;
102 const PRERELEASE_MASK: u16 = Self::PRERELEASE_FIRST_UNMASKED_BIT - 1;
103 const PRERELEASE_MINOR_MAX: u16 = (1 << Self::PRERELEASE_BITS_OFFSET) - 1;
104
105 fn try_pack(
106 minor: u16,
107 patch: u16,
108 prerelease: &Prerelease,
109 ) -> Result<(Self, u16), PackedMinorPackError> {
110 if minor > Self::PRERELEASE_MINOR_MAX {
111 return Err(PackedMinorPackError::MinorTooLarge);
112 }
113 if !prerelease.patch_is_valid(patch) {
114 return Err(PackedMinorPackError::InvalidPatchForPrerelease);
115 }
116 let (prerelease_encode_tag, patch) = match *prerelease {
117 Prerelease::Stable => (Prerelease::ENCODE_TAG_STABLE, patch),
118 Prerelease::ReleaseCandidate(rc) => (Prerelease::ENCODE_TAG_RELEASE_CANDIDATE, rc),
119 Prerelease::Beta(beta) => (Prerelease::ENCODE_TAG_BETA, beta),
120 Prerelease::Alpha(alpha) => (Prerelease::ENCODE_TAG_ALPHA, alpha),
121 };
122 let packed_minor = minor | prerelease_encode_tag << Self::PRERELEASE_BITS_OFFSET;
123 Ok((Self(packed_minor), patch))
124 }
125
126 fn try_unpack(self, patch: u16) -> Result<(u16, u16, Prerelease), PackedMinorUnpackError> {
127 let Self(packed_minor) = self;
128 let shifted_prerelease_bits = packed_minor >> Self::PRERELEASE_BITS_OFFSET;
129
130 let reserved_bits = shifted_prerelease_bits & !Self::PRERELEASE_MASK;
131 if reserved_bits != 0 {
132 return Err(PackedMinorUnpackError::ReservedBitsSet);
133 }
134
135 let prerelease_variant = shifted_prerelease_bits & Self::PRERELEASE_MASK;
136 let minor = packed_minor & !(Self::PRERELEASE_MASK << Self::PRERELEASE_BITS_OFFSET);
137
138 let (patch, prerelease) = match prerelease_variant {
139 Prerelease::ENCODE_TAG_STABLE => (patch, Prerelease::Stable),
140 Prerelease::ENCODE_TAG_RELEASE_CANDIDATE => (0, Prerelease::ReleaseCandidate(patch)),
141 Prerelease::ENCODE_TAG_BETA => (0, Prerelease::Beta(patch)),
142 Prerelease::ENCODE_TAG_ALPHA => (0, Prerelease::Alpha(patch)),
143 Self::PRERELEASE_FIRST_UNMASKED_BIT..=u16::MAX => unreachable!(),
144 };
145 Ok((minor, patch, prerelease))
146 }
147}
148
149#[derive(Debug, Clone, PartialEq, Eq)]
150pub struct Version {
151 major: u16,
152 minor: u16,
153 patch: u16,
154 commit: u32,
155 feature_set: u32,
156 client: ClientId,
157 prerelease: Prerelease,
158}
159
160impl Version {
161 fn new_from_parts(
162 major: u16,
163 minor: u16,
164 patch: u16,
165 commit: u32,
166 feature_set: u32,
167 client: ClientId,
168 prerelease: Prerelease,
169 ) -> Self {
170 assert!(prerelease.patch_is_valid(patch));
171 Self {
172 major,
173 minor,
174 patch,
175 commit,
176 feature_set,
177 client,
178 prerelease,
179 }
180 }
181
182 pub fn this_build() -> Self {
183 Self::new_from_parts(
184 env!("CARGO_PKG_VERSION_MAJOR").parse().unwrap(),
185 env!("CARGO_PKG_VERSION_MINOR").parse().unwrap(),
186 env!("CARGO_PKG_VERSION_PATCH").parse().unwrap(),
187 compute_commit(option_env!("CI_COMMIT"))
188 .or(compute_commit(option_env!("AGAVE_GIT_COMMIT_HASH")))
189 .unwrap_or_else(|| thread_rng().gen::<u32>()),
190 u32::from_le_bytes(agave_feature_set::ID.as_ref()[..4].try_into().unwrap()),
191 ClientId::Agave,
192 Prerelease::from_str(env!("CARGO_PKG_VERSION_PRE")).unwrap(),
193 )
194 }
195
196 pub fn major(&self) -> u16 {
197 self.major
198 }
199
200 pub fn minor(&self) -> u16 {
201 self.minor
202 }
203
204 pub fn patch(&self) -> u16 {
205 self.patch
206 }
207
208 pub fn commit(&self) -> u32 {
209 self.commit
210 }
211
212 pub fn feature_set(&self) -> u32 {
213 self.feature_set
214 }
215
216 pub fn client(&self) -> &ClientId {
217 &self.client
218 }
219
220 pub fn prerelease(&self) -> &Prerelease {
221 &self.prerelease
222 }
223
224 pub fn as_semver_string(&self) -> String {
225 format!("{self}")
226 }
227
228 pub fn as_detailed_string(&self) -> String {
229 format!(
230 "{} (src:{:08x}; feat:{:08x}, client:{:?})",
231 self, self.commit, self.feature_set, self.client,
232 )
233 }
234
235 pub fn as_semver_version(&self) -> semver::Version {
236 let major = u64::from(self.major);
237 let minor = u64::from(self.minor);
238 let patch = u64::from(self.patch);
239 let pre = semver::Prerelease::new(&self.prerelease.to_string())
240 .expect("solana_version::Prerelease is semver::Prerelease-compatible");
241 let build = semver::BuildMetadata::EMPTY;
242 semver::Version {
243 major,
244 minor,
245 patch,
246 pre,
247 build,
248 }
249 }
250}
251
252impl From<v3::Version> for Version {
253 fn from(other: v3::Version) -> Self {
254 let client = other.client();
255 let v3::Version {
256 major,
257 minor,
258 patch,
259 commit,
260 feature_set,
261 ..
262 } = other;
263 let (minor, patch, prerelease) =
264 PackedMinor(minor)
265 .try_unpack(patch)
266 .unwrap_or((minor, patch, Prerelease::Stable));
267 Version::new_from_parts(major, minor, patch, commit, feature_set, client, prerelease)
268 }
269}
270
271impl Default for Version {
272 fn default() -> Self {
273 Self::this_build()
274 }
275}
276
277impl fmt::Display for Version {
278 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
279 let sep = if self.prerelease == Prerelease::Stable {
280 ""
281 } else {
282 "-"
283 };
284 write!(
285 f,
286 "{}.{}.{}{}{}",
287 self.major, self.minor, self.patch, sep, self.prerelease
288 )
289 }
290}
291
292impl Sanitize for Version {}
293#[cfg_attr(feature = "frozen-abi", derive(AbiExample))]
294#[derive(Deserialize, Serialize)]
295struct SerializedVersion {
296 #[serde(with = "serde_varint")]
297 major: u16,
298 #[serde(rename = "minor")]
299 packed_minor: PackedMinor,
300 #[serde(with = "serde_varint")]
301 patch: u16,
302 commit: u32,
303 feature_set: u32,
304 #[serde(with = "serde_varint")]
305 client: u16,
306}
307
308impl Serialize for Version {
309 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
310 where
311 S: Serializer,
312 {
313 let &Version {
314 major,
315 minor,
316 patch,
317 commit,
318 feature_set,
319 ref client,
320 ref prerelease,
321 } = self;
322
323 let (packed_minor, patch) = PackedMinor::try_pack(minor, patch, prerelease)
324 .map_err(|err| S::Error::custom(format!("{err:?}")))?;
325 let client = u16::try_from(*client).map_err(S::Error::custom)?;
326
327 let serialized_version = SerializedVersion {
328 major,
329 packed_minor,
330 patch,
331 commit,
332 feature_set,
333 client,
334 };
335
336 SerializedVersion::serialize(&serialized_version, serializer)
337 }
338}
339
340impl<'de> Deserialize<'de> for Version {
341 fn deserialize<D>(deserializer: D) -> Result<Version, D::Error>
342 where
343 D: Deserializer<'de>,
344 {
345 let SerializedVersion {
346 major,
347 packed_minor,
348 patch,
349 commit,
350 feature_set,
351 client,
352 } = SerializedVersion::deserialize(deserializer)?;
353
354 let (minor, patch, prerelease) = packed_minor
355 .try_unpack(patch)
356 .map_err(|err| D::Error::custom(format!("{err:?}")))?;
357 let client = ClientId::from(client);
358
359 Ok(Version {
360 major,
361 minor,
362 patch,
363 commit,
364 feature_set,
365 client,
366 prerelease,
367 })
368 }
369}
370
371#[cfg(test)]
372mod tests {
373 use super::*;
374
375 #[test]
376 fn test_prerelease_patch_is_valid() {
377 assert!(Prerelease::Alpha(0).patch_is_valid(0));
378 assert!(Prerelease::Beta(0).patch_is_valid(0));
379 assert!(Prerelease::ReleaseCandidate(0).patch_is_valid(0));
380 assert!(Prerelease::Stable.patch_is_valid(0));
381 assert!(Prerelease::Stable.patch_is_valid(1));
382 assert!(Prerelease::Stable.patch_is_valid(u16::MAX));
383
384 assert!(!Prerelease::ReleaseCandidate(0).patch_is_valid(1));
385 assert!(!Prerelease::Beta(0).patch_is_valid(1));
386 assert!(!Prerelease::Alpha(0).patch_is_valid(1));
387 }
388
389 #[test]
390 fn test_prerelease_display() {
391 assert!(format!("{}", Prerelease::Stable).is_empty());
392 assert_eq!(format!("{}", Prerelease::ReleaseCandidate(0)), "rc.0",);
393 assert_eq!(format!("{}", Prerelease::Beta(1)), "beta.1",);
394 assert_eq!(format!("{}", Prerelease::Alpha(2)), "alpha.2",);
395 assert_eq!(format!("{}", Prerelease::Alpha(u16::MAX)), "alpha.65535",);
396 }
397
398 #[test]
399 fn test_prerelease_from_str() {
400 assert_eq!(Prerelease::from_str(""), Ok(Prerelease::Stable));
401 assert_eq!(
402 Prerelease::from_str("rc.0"),
403 Ok(Prerelease::ReleaseCandidate(0))
404 );
405 assert_eq!(Prerelease::from_str("beta.1"), Ok(Prerelease::Beta(1)));
406 assert_eq!(Prerelease::from_str("alpha.2"), Ok(Prerelease::Alpha(2)));
407 assert_eq!(
408 Prerelease::from_str("alpha.65535"),
409 Ok(Prerelease::Alpha(u16::MAX))
410 );
411 assert_eq!(
412 Prerelease::from_str("rc0"),
413 Err(ParsePrereleaseError::DotSeparatorMissing),
414 );
415 assert_eq!(
416 Prerelease::from_str("rc.a"),
417 Err(ParsePrereleaseError::NumericPartNotAU16),
418 );
419 assert_eq!(
420 Prerelease::from_str("rc.-1"),
421 Err(ParsePrereleaseError::NumericPartNotAU16),
422 );
423 assert_eq!(
424 Prerelease::from_str("rc.65536"),
425 Err(ParsePrereleaseError::NumericPartNotAU16),
426 );
427 assert_eq!(
428 Prerelease::from_str("unknown.0"),
429 Err(ParsePrereleaseError::UnknownIdentifier),
430 );
431 }
432
433 #[test]
434 fn test_prerelease_roundtrips() {
435 let test_cases = [
436 ("", Prerelease::Stable),
437 ("rc.0", Prerelease::ReleaseCandidate(0)),
438 ("beta.1", Prerelease::Beta(1)),
439 ("alpha.2", Prerelease::Alpha(2)),
440 ];
441 for (test_str, test_pr) in test_cases.into_iter() {
442 let gen_pr = Prerelease::from_str(test_str).unwrap();
443 let gen_str = test_pr.to_string();
444 let regen_pr = Prerelease::from_str(&gen_str).unwrap();
445 let regen_str = gen_pr.to_string();
446
447 assert_eq!(test_str, gen_str);
448 assert_eq!(test_str, regen_str);
449 assert_eq!(test_pr, gen_pr);
450 assert_eq!(test_pr, regen_pr);
451 }
452 }
453
454 #[test]
455 fn test_prerelease_compatible_with_semver_prerelease() {
456 let test_cases = [
457 ("", Prerelease::Stable),
458 ("rc.0", Prerelease::ReleaseCandidate(0)),
459 ("beta.1", Prerelease::Beta(1)),
460 ("alpha.2", Prerelease::Alpha(2)),
461 ];
462 for (test_str, test_pr) in test_cases.into_iter() {
463 let svpr = semver::Prerelease::new(test_pr.to_string().as_str()).unwrap();
464 assert_eq!(svpr.as_str(), test_str);
465 }
466 }
467
468 #[test]
469 fn test_packed_minor_try_pack() {
470 assert_eq!(
471 PackedMinor::try_pack(0, 0, &Prerelease::Stable),
472 Ok((PackedMinor(0), 0)),
473 );
474 assert_eq!(
475 PackedMinor::try_pack(
476 PackedMinor::PRERELEASE_MINOR_MAX,
477 u16::MAX,
478 &Prerelease::Stable,
479 ),
480 Ok((PackedMinor(0x3FFF), u16::MAX)),
481 );
482 assert_eq!(
483 PackedMinor::try_pack(0, 0, &Prerelease::ReleaseCandidate(0)),
484 Ok((PackedMinor(0x4000), 0)),
485 );
486 assert_eq!(
487 PackedMinor::try_pack(
488 PackedMinor::PRERELEASE_MINOR_MAX,
489 0,
490 &Prerelease::ReleaseCandidate(u16::MAX),
491 ),
492 Ok((PackedMinor(0x7FFF), u16::MAX)),
493 );
494 assert_eq!(
495 PackedMinor::try_pack(0, 0, &Prerelease::Beta(0)),
496 Ok((PackedMinor(0x8000), 0)),
497 );
498 assert_eq!(
499 PackedMinor::try_pack(
500 PackedMinor::PRERELEASE_MINOR_MAX,
501 0,
502 &Prerelease::Beta(u16::MAX),
503 ),
504 Ok((PackedMinor(0xBFFF), u16::MAX)),
505 );
506 assert_eq!(
507 PackedMinor::try_pack(0, 0, &Prerelease::Alpha(0)),
508 Ok((PackedMinor(0xC000), 0)),
509 );
510 assert_eq!(
511 PackedMinor::try_pack(
512 PackedMinor::PRERELEASE_MINOR_MAX,
513 0,
514 &Prerelease::Alpha(u16::MAX),
515 ),
516 Ok((PackedMinor(0xFFFF), u16::MAX)),
517 );
518
519 assert_eq!(
520 PackedMinor::try_pack(
521 PackedMinor::PRERELEASE_MINOR_MAX + 1,
522 0,
523 &Prerelease::Stable
524 ),
525 Err(PackedMinorPackError::MinorTooLarge),
526 );
527 assert_eq!(
528 PackedMinor::try_pack(0, 1, &Prerelease::Beta(0),),
529 Err(PackedMinorPackError::InvalidPatchForPrerelease),
530 );
531 }
532
533 #[test]
534 fn test_packed_minor_try_unpack() {
535 assert_eq!(
536 PackedMinor(0x0000).try_unpack(0),
537 Ok((0, 0, Prerelease::Stable))
538 );
539 assert_eq!(
540 PackedMinor(0x3FFF).try_unpack(u16::MAX),
541 Ok((
542 PackedMinor::PRERELEASE_MINOR_MAX,
543 u16::MAX,
544 Prerelease::Stable
545 )),
546 );
547 assert_eq!(
548 PackedMinor(0x4000).try_unpack(0),
549 Ok((0, 0, Prerelease::ReleaseCandidate(0)))
550 );
551 assert_eq!(
552 PackedMinor(0x7FFF).try_unpack(u16::MAX),
553 Ok((
554 PackedMinor::PRERELEASE_MINOR_MAX,
555 0,
556 Prerelease::ReleaseCandidate(u16::MAX)
557 )),
558 );
559 assert_eq!(
560 PackedMinor(0x8000).try_unpack(0),
561 Ok((0, 0, Prerelease::Beta(0)))
562 );
563 assert_eq!(
564 PackedMinor(0xBFFF).try_unpack(u16::MAX),
565 Ok((
566 PackedMinor::PRERELEASE_MINOR_MAX,
567 0,
568 Prerelease::Beta(u16::MAX)
569 )),
570 );
571 assert_eq!(
572 PackedMinor(0xC000).try_unpack(0),
573 Ok((0, 0, Prerelease::Alpha(0)))
574 );
575 assert_eq!(
576 PackedMinor(0xFFFF).try_unpack(u16::MAX),
577 Ok((
578 PackedMinor::PRERELEASE_MINOR_MAX,
579 0,
580 Prerelease::Alpha(u16::MAX)
581 )),
582 );
583
584 if PackedMinor::PRERELEASE_BITS_OFFSET + PackedMinor::PRERELEASE_MASK_BITS < u16::BITS {
587 unimplemented!("current configuration leaves no bits reserved");
588 }
589 }
590
591 fn prerelease_stable(_v: u16, patch: u16) -> (Prerelease, u16) {
592 (Prerelease::Stable, patch)
593 }
594
595 fn prerelease_release_candidate(v: u16, _patch: u16) -> (Prerelease, u16) {
596 (Prerelease::ReleaseCandidate(v), 0)
597 }
598
599 fn prerelease_beta(v: u16, _patch: u16) -> (Prerelease, u16) {
600 (Prerelease::Beta(v), 0)
601 }
602
603 fn prerelease_alpha(v: u16, _patch: u16) -> (Prerelease, u16) {
604 (Prerelease::Alpha(v), 0)
605 }
606
607 #[test]
608 fn test_packed_minor_roundtrips() {
609 let test_cases = [
610 prerelease_stable,
611 prerelease_release_candidate,
612 prerelease_beta,
613 prerelease_alpha,
614 ];
615 for prerelease_ctor in &test_cases {
616 for minor in [0, 1, PackedMinor::PRERELEASE_MINOR_MAX] {
617 for patch in [0, 1, u16::MAX] {
618 for prerelease in [0, 1, u16::MAX] {
619 let (prerelease, patch) = prerelease_ctor(prerelease, patch);
620 let (packed_minor, packed_patch) =
621 PackedMinor::try_pack(minor, patch, &prerelease).unwrap();
622 let unpacked = packed_minor.try_unpack(packed_patch).unwrap();
623 assert_eq!((minor, patch, prerelease), unpacked);
624 }
625 }
626 }
627 }
628 }
629
630 #[test]
631 fn test_v3_and_v4_same_size() {
632 let v3_version = v3::Version {
634 major: 0,
635 minor: 0,
636 patch: 0,
637 commit: 0,
638 feature_set: 0,
639 client: 0,
640 };
641 let v4_version =
642 Version::new_from_parts(0, 0, 0, 0, 0, ClientId::Agave, Prerelease::Stable);
643 assert_eq!(
644 bincode::serialized_size(&v3_version).unwrap(),
645 bincode::serialized_size(&v4_version).unwrap(),
646 );
647
648 let v3_version = v3::Version {
650 major: u16::MAX,
651 minor: u16::MAX,
652 patch: u16::MAX,
653 commit: u32::MAX,
654 feature_set: u32::MAX,
655 client: u16::MAX,
656 };
657 let v4_version = Version::new_from_parts(
658 u16::MAX,
659 PackedMinor::PRERELEASE_MINOR_MAX,
660 0,
661 u32::MAX,
662 u32::MAX,
663 ClientId::Unknown(u16::MAX),
664 Prerelease::Alpha(u16::MAX),
665 );
666 assert_eq!(
667 bincode::serialized_size(&v3_version).unwrap(),
668 bincode::serialized_size(&v4_version).unwrap(),
669 );
670 }
671
672 #[test]
673 fn test_v4_from_v3() {
674 let major = 3;
675 let minor = 2;
676 let patch = 1;
677 let alpha = 1;
678 let beta = 2;
679 let rc = 3;
680 let commit = 0;
681 let feature_set = 0;
682 let client = ClientId::Agave;
683
684 let v3_version = v3::Version {
685 major,
686 minor: minor | Prerelease::ENCODE_TAG_ALPHA << PackedMinor::PRERELEASE_BITS_OFFSET,
687 patch: alpha,
688 commit,
689 feature_set,
690 client: u16::try_from(client).expect("agave client id"),
691 };
692 assert_eq!(
693 Version::from(v3_version),
694 Version::new_from_parts(
695 major,
696 minor,
697 0,
698 commit,
699 feature_set,
700 client,
701 Prerelease::Alpha(alpha),
702 ),
703 );
704
705 let v3_version = v3::Version {
706 major,
707 minor: minor | Prerelease::ENCODE_TAG_BETA << PackedMinor::PRERELEASE_BITS_OFFSET,
708 patch: beta,
709 commit,
710 feature_set,
711 client: u16::try_from(client).expect("agave client id"),
712 };
713 assert_eq!(
714 Version::from(v3_version),
715 Version::new_from_parts(
716 major,
717 minor,
718 0,
719 commit,
720 feature_set,
721 client,
722 Prerelease::Beta(beta),
723 ),
724 );
725
726 let v3_version = v3::Version {
727 major,
728 minor: minor
729 | Prerelease::ENCODE_TAG_RELEASE_CANDIDATE << PackedMinor::PRERELEASE_BITS_OFFSET,
730 patch: rc,
731 commit,
732 feature_set,
733 client: u16::try_from(client).expect("agave client id"),
734 };
735 assert_eq!(
736 Version::from(v3_version),
737 Version::new_from_parts(
738 major,
739 minor,
740 0,
741 commit,
742 feature_set,
743 client,
744 Prerelease::ReleaseCandidate(rc),
745 ),
746 );
747
748 let v3_version = v3::Version {
749 major,
750 minor,
751 patch,
752 commit,
753 feature_set,
754 client: u16::try_from(client).expect("agave client id"),
755 };
756 assert_eq!(
757 Version::from(v3_version),
758 Version::new_from_parts(
759 major,
760 minor,
761 patch,
762 commit,
763 feature_set,
764 client,
765 Prerelease::Stable,
766 ),
767 );
768 }
769
770 #[test]
771 fn test_serde() {
772 let version =
773 Version::new_from_parts(0, 0, 0, 0, 0, ClientId::SolanaLabs, Prerelease::Stable);
774
775 let bytes = bincode::serialize(&version).unwrap();
776 assert_eq!(bytes, [0u8; 12]);
777
778 let de_version: Version = bincode::deserialize(&bytes).unwrap();
779 assert_eq!(version, de_version);
780
781 let version = Version::new_from_parts(
782 u16::MAX,
783 PackedMinor::PRERELEASE_MINOR_MAX,
784 0,
785 u32::MAX,
786 u32::MAX,
787 ClientId::Unknown(u16::MAX),
788 Prerelease::Alpha(u16::MAX),
789 );
790
791 let bytes = bincode::serialize(&version).unwrap();
792 assert_eq!(
793 bytes,
794 [
795 0xff, 0xff, 0x03, 0xff, 0xff, 0x03, 0xff, 0xff, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff,
796 0xff, 0xff, 0xff, 0xff, 0xff, 0x03,
797 ]
798 );
799
800 let de_version: Version = bincode::deserialize(&bytes).unwrap();
801 assert_eq!(version, de_version);
802 }
803
804 #[test]
805 fn test_version_as_detailed_string() {
806 let version = Version::new_from_parts(0, 0, 0, 0, 0, ClientId::Agave, Prerelease::Stable);
807 assert_eq!(
808 version.as_detailed_string(),
809 "0.0.0 (src:00000000; feat:00000000, client:Agave)",
810 );
811
812 let version = Version::new_from_parts(
813 0,
814 0,
815 0,
816 0,
817 0,
818 ClientId::Agave,
819 Prerelease::ReleaseCandidate(0),
820 );
821 assert_eq!(
822 version.as_detailed_string(),
823 "0.0.0-rc.0 (src:00000000; feat:00000000, client:Agave)",
824 );
825 }
826}