Skip to main content

turbopack_core/chunk/
mod.rs

1pub mod availability_info;
2pub mod available_modules;
3pub mod chunk_group;
4pub mod chunk_id_strategy;
5pub(crate) mod chunk_item_batch;
6pub mod chunking;
7pub(crate) mod chunking_context;
8pub(crate) mod data;
9pub(crate) mod evaluate;
10
11use std::{fmt::Display, hash::Hash};
12
13use anyhow::{Result, bail};
14use auto_hash_map::AutoSet;
15use bincode::{Decode, Encode};
16use serde::{Deserialize, Serialize};
17use turbo_rcstr::RcStr;
18use turbo_tasks::{
19    FxIndexSet, NonLocalValue, ReadRef, ResolvedVc, Upcast, ValueToString, Vc,
20    debug::ValueDebugFormat, trace::TraceRawVcs,
21};
22use turbo_tasks_hash::DeterministicHash;
23
24pub use crate::chunk::{
25    chunk_item_batch::{
26        ChunkItemBatchGroup, ChunkItemBatchWithAsyncModuleInfo,
27        ChunkItemOrBatchWithAsyncModuleInfo, batch_info,
28    },
29    chunking_context::{
30        AssetSuffix, ChunkGroupResult, ChunkGroupType, ChunkingConfig, ChunkingConfigs,
31        ChunkingContext, ChunkingContextExt, EntryChunkGroupResult, MangleType, MinifyType,
32        SourceMapSourceType, SourceMapsType, UnusedReferences, UrlBehavior,
33        WorkerConfigurationOptions,
34    },
35    data::{ChunkData, ChunkDataOption, ChunksData},
36    evaluate::{EvaluatableAsset, EvaluatableAssetExt, EvaluatableAssets},
37};
38use crate::{
39    asset::Asset,
40    chunk::{availability_info::AvailabilityInfo, available_modules::AvailableModulesSet},
41    emit_collect::CollectingModule,
42    ident::AssetIdent,
43    module::Module,
44    module_graph::{
45        ModuleGraph,
46        module_batch::{ChunkableModuleOrBatch, ModuleBatchGroup},
47    },
48    output::{OutputAssets, OutputAssetsReference},
49};
50
51#[turbo_tasks::task_input]
52#[derive(
53    Debug, Clone, Copy, PartialEq, Eq, Hash, TraceRawVcs, DeterministicHash, Encode, Decode,
54)]
55pub enum ContentHashing {
56    /// Direct content hashing: Embeds the chunk content hash directly into the referencing chunk.
57    /// Benefit: No hash manifest needed.
58    /// Downside: Causes cascading hash invalidation.
59    Direct {
60        /// The length of the content hash in base38 chars. Anything lower than 7 is not
61        /// recommended due to the high risk of collisions.
62        length: u8,
63    },
64}
65
66#[turbo_tasks::value(shared)]
67#[derive(Debug, Default, Clone, Copy, Hash, Serialize, Deserialize)]
68#[serde(rename_all = "kebab-case")]
69pub enum CrossOrigin {
70    #[default]
71    None,
72    Anonymous,
73    UseCredentials,
74}
75
76impl CrossOrigin {
77    pub fn as_str(self) -> Option<&'static str> {
78        match self {
79            Self::None => None,
80            Self::Anonymous => Some("anonymous"),
81            Self::UseCredentials => Some("use-credentials"),
82        }
83    }
84}
85
86impl TryFrom<Option<&str>> for CrossOrigin {
87    type Error = anyhow::Error;
88
89    fn try_from(value: Option<&str>) -> Result<Self> {
90        match value {
91            None => Ok(Self::None),
92            Some("anonymous") => Ok(Self::Anonymous),
93            Some("use-credentials") => Ok(Self::UseCredentials),
94            Some(value) => bail!(
95                "invalid crossOrigin value `{value}`; supported values are `anonymous` and \
96                 `use-credentials`"
97            ),
98        }
99    }
100}
101
102#[turbo_tasks::value(shared)]
103#[derive(Debug, Clone, Copy, Hash, Serialize, Deserialize)]
104pub struct ChunkLoadRetry {
105    /// Number of retry attempts after the initial load fails. `0` disables retries.
106    pub max_retry_attempts: u32,
107    /// Base delay before a retry, in milliseconds.
108    pub base_delay_ms: u32,
109    /// Maximum random jitter added to the base delay, in milliseconds.
110    pub max_jitter_ms: u32,
111}
112
113impl Default for ChunkLoadRetry {
114    fn default() -> Self {
115        // Retry a transient failure once after a short jittered delay. Network
116        // blips (a brief connection reset, a short CDN hiccup) often succeed on
117        // a second try.
118        Self {
119            max_retry_attempts: 1,
120            base_delay_ms: 200,
121            max_jitter_ms: 400,
122        }
123    }
124}
125
126/// A module id, which can be a number or string
127#[turbo_tasks::value(shared, operation)]
128#[derive(Debug, Clone, Hash, Ord, PartialOrd, DeterministicHash, Serialize, ValueToString)]
129#[serde(untagged)]
130pub enum ModuleId {
131    Number(u64),
132    String(RcStr),
133}
134
135impl Display for ModuleId {
136    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
137        match self {
138            ModuleId::Number(i) => write!(f, "{i}"),
139            ModuleId::String(s) => write!(f, "{s}"),
140        }
141    }
142}
143
144impl ModuleId {
145    pub fn parse(id: &str) -> Result<ModuleId> {
146        Ok(match id.parse::<u64>() {
147            Ok(i) => ModuleId::Number(i),
148            Err(_) => ModuleId::String(id.into()),
149        })
150    }
151}
152
153/// A list of module ids.
154#[turbo_tasks::value(transparent, shared)]
155pub struct ModuleIds(Vec<ModuleId>);
156
157/// A [Module] that can be converted into a [ChunkItem].
158#[turbo_tasks::value_trait]
159pub trait ChunkableModule: Module {
160    #[turbo_tasks::function]
161    fn as_chunk_item(
162        self: Vc<Self>,
163        module_graph: Vc<ModuleGraph>,
164        chunking_context: Vc<Box<dyn ChunkingContext>>,
165    ) -> Vc<Box<dyn ChunkItem>>;
166}
167
168/// A [Module] that can be merged with other [Module]s (to perform scope hoisting)
169// TODO currently this is only used for ecmascript modules, and with the current API cannot be used
170// with other module types (as a MergeableModule cannot prevent itself from being merged with other
171// module types)
172#[turbo_tasks::value_trait]
173pub trait MergeableModule: Module {
174    /// Even though MergeableModule is implemented, this allows a dynamic condition to determine
175    /// mergeability
176    #[turbo_tasks::function]
177    fn is_mergeable(self: Vc<Self>) -> Vc<bool> {
178        Vc::cell(true)
179    }
180
181    /// Create a new module representing the merged content of the given `modules`.
182    ///
183    /// Group entry points are not referenced by any other module in the group. This list is needed
184    /// because the merged module is created by recursively inlining modules when they are imported,
185    /// but this process has to start somewhere (= with these entry points).
186    #[turbo_tasks::function]
187    fn merge(
188        self: Vc<Self>,
189        modules: Vc<MergeableModulesExposed>,
190        entry_points: Vc<MergeableModules>,
191    ) -> Vc<Box<dyn ChunkableModule>>;
192}
193#[turbo_tasks::value(transparent)]
194pub struct MergeableModules(Vec<ResolvedVc<Box<dyn MergeableModule>>>);
195
196#[turbo_tasks::value_impl]
197impl MergeableModules {
198    #[turbo_tasks::function]
199    pub fn interned(modules: Vec<ResolvedVc<Box<dyn MergeableModule>>>) -> Vc<Self> {
200        Vc::cell(modules)
201    }
202}
203
204/// Whether a given module needs to be exposed (depending on how it is imported by other modules)
205#[turbo_tasks::task_input]
206#[derive(Copy, Clone, Debug, PartialEq, Eq, TraceRawVcs, Hash, Encode, Decode)]
207pub enum MergeableModuleExposure {
208    // This module is only used from within the current group, and only individual exports are
209    // used (and no namespace object is required).
210    None,
211    // This module is only used from within the current group, and but the namespace object is
212    // needed.
213    Internal,
214    // The exports of this module are read from outside this group (necessitating a namespace
215    // object anyway).
216    External,
217}
218
219#[turbo_tasks::value(transparent)]
220pub struct MergeableModulesExposed(
221    Vec<(
222        ResolvedVc<Box<dyn MergeableModule>>,
223        MergeableModuleExposure,
224    )>,
225);
226
227#[turbo_tasks::value_impl]
228impl MergeableModulesExposed {
229    #[turbo_tasks::function]
230    pub fn interned(
231        modules: Vec<(
232            ResolvedVc<Box<dyn MergeableModule>>,
233            MergeableModuleExposure,
234        )>,
235    ) -> Vc<Self> {
236        Vc::cell(modules)
237    }
238}
239
240#[turbo_tasks::value(transparent)]
241pub struct Chunks(Vec<ResolvedVc<Box<dyn Chunk>>>);
242
243#[turbo_tasks::value_impl]
244impl Chunks {
245    #[turbo_tasks::function]
246    pub fn empty() -> Vc<Self> {
247        Vc::cell(vec![])
248    }
249}
250
251/// Groups chunk items together into something that will become an [`OutputAsset`]. It usually
252/// contains multiple chunk items.
253///
254/// [`OutputAsset`]: crate::output::OutputAsset
255//
256// TODO: This could be simplified to and merged with OutputChunk
257#[turbo_tasks::value_trait]
258pub trait Chunk: OutputAssetsReference {
259    #[turbo_tasks::function]
260    fn ident(self: Vc<Self>) -> Vc<AssetIdent>;
261
262    #[turbo_tasks::function]
263    fn chunking_context(self: Vc<Self>) -> Vc<Box<dyn ChunkingContext>>;
264
265    #[turbo_tasks::function]
266    fn chunk_items(self: Vc<Self>) -> Vc<ChunkItems> {
267        ChunkItems(vec![]).cell()
268    }
269}
270
271/// Aggregated information about a chunk content that can be used by the runtime
272/// code to optimize chunk loading.
273#[turbo_tasks::value(shared)]
274#[derive(Default)]
275pub struct OutputChunkRuntimeInfo {
276    pub included_ids: Option<ResolvedVc<ModuleIds>>,
277    pub excluded_ids: Option<ResolvedVc<ModuleIds>>,
278    /// List of paths of chunks containing individual modules that are part of
279    /// this chunk. This is useful for selectively loading modules from a chunk
280    /// without loading the whole chunk.
281    pub module_chunks: Option<ResolvedVc<OutputAssets>>,
282    pub placeholder_for_future_extensions: (),
283}
284
285#[turbo_tasks::value_impl]
286impl OutputChunkRuntimeInfo {
287    #[turbo_tasks::function]
288    pub fn empty() -> Vc<Self> {
289        Self::default().cell()
290    }
291}
292
293#[turbo_tasks::value_trait]
294pub trait OutputChunk: Asset {
295    #[turbo_tasks::function]
296    fn runtime_info(self: Vc<Self>) -> Vc<OutputChunkRuntimeInfo>;
297}
298
299/// Whether this reference is an entry point for a traced subgraph.
300#[derive(
301    Debug,
302    Clone,
303    Copy,
304    Hash,
305    TraceRawVcs,
306    Serialize,
307    Deserialize,
308    Eq,
309    PartialEq,
310    ValueDebugFormat,
311    Encode,
312    Decode,
313)]
314#[turbo_tasks::task_input]
315pub enum TracedMode {
316    /// Going from bundled to unbundled code, i.e. an external dependency or readFile static assets.
317    Entry,
318    /// This reference should only be respected from unbundled code (e.g. for package.json needed by
319    /// externals (sort of affecting_sources)
320    Transitive,
321}
322
323impl Display for TracedMode {
324    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
325        match self {
326            TracedMode::Entry => write!(f, "Entry"),
327            TracedMode::Transitive => write!(f, "Transitive"),
328        }
329    }
330}
331
332/// Specifies how a chunk interacts with other chunks when building a chunk
333/// group
334#[derive(
335    Debug,
336    Clone,
337    Hash,
338    TraceRawVcs,
339    Serialize,
340    Deserialize,
341    Eq,
342    PartialEq,
343    ValueDebugFormat,
344    NonLocalValue,
345    Encode,
346    Decode,
347)]
348pub enum ChunkingType {
349    /// The referenced module is placed in the same chunk group and is loaded in parallel.
350    Parallel {
351        /// Whether the parent module becomes an async module when the referenced module is async.
352        /// This should happen for e.g. ESM imports, but not for CommonJS requires.
353        inherit_async: bool,
354        /// Whether the referenced module is executed always immediately before the parent module
355        /// (corresponding to ESM import semantics).
356        hoisted: bool,
357    },
358    /// An async loader is placed into the referencing chunk and loads the
359    /// separate chunk group in which the module is placed.
360    Async,
361    /// Create a new chunk group in a separate context, merging references with the same tag into a
362    /// single chunk group. It does not inherit the available modules from the parent.
363    // TODO this is currently skipped in chunking
364    Isolated {
365        _ty: ChunkGroupType,
366        merge_tag: Option<RcStr>,
367    },
368    /// Declare an emitted module (corresponds to __turboack_emit__).
369    Emitted {
370        namespace: RcStr,
371        /// false = emit to current entry, true = emit to all entries
372        emit_to_all_entries: bool,
373    },
374    /// During the build process, edges with ChunkingType::Emitted are collected and reattached to
375    /// the collecting module. These should not be used manually in a reference.
376    Collected { namespace: RcStr },
377    /// Chunk this reference once per entry, like async loaders.
378    PerEntry,
379    /// Create a new chunk group in a separate context, merging references with the same tag into a
380    /// single chunk group. It provides available modules to the current chunk group. It's assumed
381    /// to be loaded before the current chunk group.
382    Shared {
383        inherit_async: bool,
384        merge_tag: Option<RcStr>,
385    },
386    /// The module not placed in chunk group, but its references are still followed. This is used
387    /// for NFT, to list all unbundled files that are still needed at runtime (some static assets,
388    /// or externals and their transitive dependencies).
389    Traced {
390        /// Whether this reference is an entry point for a traced subgraph.
391        mode: TracedMode,
392    },
393}
394
395impl Display for ChunkingType {
396    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
397        match self {
398            ChunkingType::Parallel {
399                inherit_async,
400                hoisted,
401            } => {
402                write!(
403                    f,
404                    "Parallel(inherit_async: {inherit_async}, hoisted: {hoisted})",
405                )
406            }
407            ChunkingType::Async => write!(f, "Async"),
408            ChunkingType::PerEntry => write!(f, "PerEntry"),
409            ChunkingType::Isolated {
410                _ty,
411                merge_tag: Some(merge_tag),
412            } => {
413                write!(f, "Isolated(merge_tag: {merge_tag})")
414            }
415            ChunkingType::Isolated {
416                _ty,
417                merge_tag: None,
418            } => {
419                write!(f, "Isolated")
420            }
421            ChunkingType::Emitted {
422                namespace,
423                emit_to_all_entries,
424            } => {
425                write!(
426                    f,
427                    "Emitted(namespace: {namespace}, emit_to_all_entries: {emit_to_all_entries})"
428                )
429            }
430            ChunkingType::Collected { namespace } => {
431                write!(f, "Collected(namespace: {namespace})")
432            }
433            ChunkingType::Shared {
434                inherit_async,
435                merge_tag: Some(merge_tag),
436            } => {
437                write!(
438                    f,
439                    "Shared(inherit_async: {inherit_async}, merge_tag: {merge_tag})"
440                )
441            }
442            ChunkingType::Shared {
443                inherit_async,
444                merge_tag: None,
445            } => {
446                write!(f, "Shared(inherit_async: {inherit_async})")
447            }
448            ChunkingType::Traced { mode } => write!(f, "Traced(mode: {mode})"),
449        }
450    }
451}
452
453impl ChunkingType {
454    pub fn is_inherit_async(&self) -> bool {
455        matches!(
456            self,
457            ChunkingType::Parallel {
458                inherit_async: true,
459                ..
460            } | ChunkingType::Shared {
461                inherit_async: true,
462                ..
463            }
464        )
465    }
466
467    pub fn is_parallel(&self) -> bool {
468        matches!(self, ChunkingType::Parallel { .. })
469    }
470
471    pub fn is_traced(&self) -> bool {
472        matches!(self, ChunkingType::Traced { .. })
473    }
474
475    pub fn is_merged(&self) -> bool {
476        matches!(
477            self,
478            ChunkingType::Isolated {
479                merge_tag: Some(_),
480                ..
481            } | ChunkingType::Shared {
482                merge_tag: Some(_),
483                ..
484            }
485        )
486    }
487
488    pub fn without_inherit_async(&self) -> Self {
489        match self {
490            ChunkingType::Parallel { hoisted, .. } => ChunkingType::Parallel {
491                hoisted: *hoisted,
492                inherit_async: false,
493            },
494            ChunkingType::Async => ChunkingType::Async,
495            ChunkingType::PerEntry => ChunkingType::PerEntry,
496            ChunkingType::Isolated { _ty, merge_tag } => ChunkingType::Isolated {
497                _ty: *_ty,
498                merge_tag: merge_tag.clone(),
499            },
500            ChunkingType::Emitted {
501                namespace,
502                emit_to_all_entries,
503            } => ChunkingType::Emitted {
504                namespace: namespace.clone(),
505                emit_to_all_entries: *emit_to_all_entries,
506            },
507            ChunkingType::Collected { namespace } => ChunkingType::Collected {
508                namespace: namespace.clone(),
509            },
510            ChunkingType::Shared {
511                inherit_async: _,
512                merge_tag,
513            } => ChunkingType::Shared {
514                inherit_async: false,
515                merge_tag: merge_tag.clone(),
516            },
517            ChunkingType::Traced { mode } => ChunkingType::Traced { mode: *mode },
518        }
519    }
520}
521
522/// The modules (soon to be chunk items) that were discovered after traversing a given chunk group.
523#[turbo_tasks::value(cell = "new")]
524pub struct ChunkGroupContentInner {
525    /// Regular chunkable modules/module batches
526    pub chunkable_items: Vec<ChunkableModuleOrBatch>,
527    /// As an optimization, we also keep track of the batch groups that were discovered.
528    pub batch_groups: Vec<ResolvedVc<ModuleBatchGroup>>,
529    /// The modules that were imported with ChunkingType::Async
530    #[bincode(with = "turbo_bincode::indexset")]
531    pub async_modules: FxIndexSet<ResolvedVc<Box<dyn ChunkableModule>>>,
532    /// All modules that implement CollectingModule
533    #[bincode(with = "turbo_bincode::indexset")]
534    pub collecting_modules: FxIndexSet<ResolvedVc<Box<dyn CollectingModule>>>,
535    pub available_modules: ResolvedVc<AvailableModulesSet>,
536}
537
538pub struct ChunkGroupContent {
539    pub inner: ReadRef<ChunkGroupContentInner>,
540    pub availability_info: AvailabilityInfo,
541}
542
543#[turbo_tasks::value_trait]
544pub trait ChunkItem: OutputAssetsReference {
545    /// The [AssetIdent] of the [Module] that this [ChunkItem] was created from.
546    /// For most chunk types this must uniquely identify the chunk item at
547    /// runtime as it's the source of the module id used at runtime.
548    #[turbo_tasks::function]
549    fn asset_ident(self: Vc<Self>) -> Vc<AssetIdent>;
550
551    /// A [AssetIdent] that uniquely identifies the content of this [ChunkItem].
552    /// It is usually identical to [ChunkItem::asset_ident] but can be
553    /// different when the chunk item content depends on available modules e. g.
554    /// for chunk loaders.
555    #[turbo_tasks::function]
556    fn content_ident(self: Vc<Self>) -> Vc<AssetIdent> {
557        self.asset_ident()
558    }
559
560    /// The type of chunk this item should be assembled into.
561    fn ty(&self) -> Vc<Box<dyn ChunkType>>;
562
563    /// A temporary method to retrieve the module associated with this
564    /// ChunkItem. TODO: Remove this as part of the chunk refactoring.
565    #[turbo_tasks::function]
566    fn module(self: Vc<Self>) -> Vc<Box<dyn Module>>;
567
568    fn chunking_context(&self) -> Vc<Box<dyn ChunkingContext>>;
569}
570
571#[turbo_tasks::value_trait]
572pub trait ChunkType: ValueToString {
573    /// Whether the source (reference) order of items needs to be retained during chunking.
574    #[turbo_tasks::function]
575    fn is_style(self: Vc<Self>) -> Vc<bool>;
576
577    /// Create a new chunk for the given chunk items
578    #[turbo_tasks::function]
579    fn chunk(
580        &self,
581        chunking_context: Vc<Box<dyn ChunkingContext>>,
582        chunk_items: Vec<ChunkItemOrBatchWithAsyncModuleInfo>,
583        batch_groups: Vec<ResolvedVc<ChunkItemBatchGroup>>,
584        component_chunks: Vec<ResolvedVc<Box<dyn Chunk>>>,
585    ) -> Vc<Box<dyn Chunk>>;
586
587    #[turbo_tasks::function]
588    fn chunk_item_size(
589        &self,
590        chunking_context: Vc<Box<dyn ChunkingContext>>,
591        chunk_item: Vc<Box<dyn ChunkItem>>,
592        async_module_info: Option<Vc<AsyncModuleInfo>>,
593    ) -> Vc<usize>;
594}
595
596pub fn round_chunk_item_size(size: usize) -> usize {
597    let a = size.next_power_of_two();
598    size & (a | (a >> 1) | (a >> 2))
599}
600
601#[turbo_tasks::value(transparent)]
602pub struct ChunkItems(pub Vec<ResolvedVc<Box<dyn ChunkItem>>>);
603
604#[turbo_tasks::value]
605pub struct AsyncModuleInfo {
606    pub referenced_async_modules: AutoSet<ResolvedVc<Box<dyn Module>>>,
607}
608
609#[turbo_tasks::value_impl]
610impl AsyncModuleInfo {
611    #[turbo_tasks::function]
612    pub fn new(referenced_async_modules: Vec<ResolvedVc<Box<dyn Module>>>) -> Result<Vc<Self>> {
613        Ok(Self {
614            referenced_async_modules: referenced_async_modules.into_iter().collect(),
615        }
616        .cell())
617    }
618}
619
620#[turbo_tasks::task_input]
621#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, TraceRawVcs, Encode, Decode)]
622pub struct ChunkItemWithAsyncModuleInfo {
623    pub chunk_item: ResolvedVc<Box<dyn ChunkItem>>,
624    pub chunk_type: ResolvedVc<Box<dyn ChunkType>>,
625    pub module: Option<ResolvedVc<Box<dyn Module>>>,
626    pub async_info: Option<ResolvedVc<AsyncModuleInfo>>,
627}
628
629pub trait ChunkItemExt {
630    /// Returns the module id of this chunk item.
631    fn id(self: Vc<Self>) -> impl Future<Output = Result<ModuleId>> + Send;
632}
633
634impl<T> ChunkItemExt for T
635where
636    T: Upcast<Box<dyn ChunkItem>> + Send,
637{
638    /// Returns the module id of this chunk item.
639    async fn id(self: Vc<Self>) -> Result<ModuleId> {
640        let chunk_item = Vc::upcast_non_strict(self);
641        chunk_item
642            .into_trait_ref()
643            .await?
644            .chunking_context()
645            .chunk_item_id_strategy()
646            .await?
647            .get_id(chunk_item)
648            .await
649    }
650}
651
652pub trait ModuleChunkItemIdExt {
653    /// Returns the chunk item id of this module.
654    fn chunk_item_id(
655        self: Vc<Self>,
656        chunking_context: Vc<Box<dyn ChunkingContext>>,
657    ) -> impl Future<Output = Result<ModuleId>> + Send;
658}
659impl<T> ModuleChunkItemIdExt for T
660where
661    T: Upcast<Box<dyn Module>> + Send,
662{
663    async fn chunk_item_id(
664        self: Vc<Self>,
665        chunking_context: Vc<Box<dyn ChunkingContext>>,
666    ) -> Result<ModuleId> {
667        chunking_context
668            .chunk_item_id_strategy()
669            .await?
670            .get_id_from_module(Vc::upcast_non_strict(self))
671            .await
672    }
673}
674
675#[cfg(test)]
676mod tests {
677    use super::*;
678
679    #[test]
680    fn test_round_chunk_item_size() {
681        assert_eq!(round_chunk_item_size(0), 0);
682        assert_eq!(round_chunk_item_size(1), 1);
683        assert_eq!(round_chunk_item_size(2), 2);
684        assert_eq!(round_chunk_item_size(3), 3);
685        assert_eq!(round_chunk_item_size(4), 4);
686        assert_eq!(round_chunk_item_size(5), 4);
687        assert_eq!(round_chunk_item_size(6), 6);
688        assert_eq!(round_chunk_item_size(7), 6);
689        assert_eq!(round_chunk_item_size(8), 8);
690        assert_eq!(round_chunk_item_size(49000), 32_768);
691        assert_eq!(round_chunk_item_size(50000), 49_152);
692
693        assert_eq!(changes_in_range(0..1000), 19);
694        assert_eq!(changes_in_range(1000..2000), 2);
695        assert_eq!(changes_in_range(2000..3000), 1);
696
697        assert_eq!(changes_in_range(3000..10000), 4);
698
699        fn changes_in_range(range: std::ops::Range<usize>) -> usize {
700            let len = range.len();
701            let mut count = 0;
702            for i in range {
703                let a = round_chunk_item_size(i);
704                assert!(a >= i * 2 / 3);
705                assert!(a <= i);
706                let b = round_chunk_item_size(i + 1);
707
708                if a == b {
709                    count += 1;
710                }
711            }
712            len - count
713        }
714    }
715}