<commit_before>use beacon_chain::{BeaconChain, BeaconChainTypes};
use environment::RuntimeContext;
use exit_future::Signal;
use futures::{Future, Stream};
use network::Service as NetworkService;
use parking_lot::Mutex;
use slog::{debug, error, info, warn};
use slot_clock::SlotClock;
use std::sync::Arc;
use std::time::{Duration, Instant};
use tokio::timer::Interval;
use types::{EthSpec, Slot};

/// Create a warning log whenever the peer count is at or below this value.
pub const WARN_PEER_COUNT: usize = 1;

const SECS_PER_MINUTE: f64 = 60.0;
const SECS_PER_HOUR: f64 = 3600.0;
const SECS_PER_DAY: f64 = 86400.0; // non-leap
const SECS_PER_WEEK: f64 = 604800.0; // non-leap
const DAYS_PER_WEEK: f64 = 7.0;
const HOURS_PER_DAY: f64 = 24.0;
const MINUTES_PER_HOUR: f64 = 60.0;

/// How long to wait for the lock on `network.libp2p_service()` before we give up.
const LIBP2P_LOCK_TIMEOUT: Duration = Duration::from_millis(50);

/// The number of historical observations that should be used to determine the average sync time.
const SPEEDO_OBSERVATIONS: usize = 4;

/// Spawns a notifier service which periodically logs information about the node.
pub fn spawn_notifier<T: BeaconChainTypes>(
    context: RuntimeContext<T::EthSpec>,
    beacon_chain: Arc<BeaconChain<T>>,
    network: Arc<NetworkService<T>>,
    milliseconds_per_slot: u64,
) -> Result<Signal, String> {
    let log_1 = context.log.clone();
    let log_2 = context.log.clone();

    let slot_duration = Duration::from_millis(milliseconds_per_slot);
    let duration_to_next_slot = beacon_chain
        .slot_clock
        .duration_to_next_slot()
        .ok_or_else(|| "slot_notifier unable to determine time to next slot")?;

    // Run this half way through each slot.
    let start_instant = Instant::now() + duration_to_next_slot + (slot_duration / 2);

    // Run this each slot.
    let interval_duration = slot_duration;

    let speedo = Mutex::new(Speedo::default());

    let interval_future = Interval::new(start_instant, interval_duration)
        .map_err(
            move |e| error!(log_1, "Slot notifier timer failed"; "error" => format!("{:?}", e)),
        )
        .for_each(move |_| {
            let log = log_2.clone();

            let connected_peer_count = if let Some(libp2p) = network
                .libp2p_service()
                .try_lock_until(Instant::now() + LIBP2P_LOCK_TIMEOUT)
            {
                libp2p.swarm.connected_peers()
            } else {
                // Use max_value here and we'll print something pretty later.
                usize::max_value()
            };

            let head = beacon_chain.head();

            let head_slot = head.beacon_block.slot;
            let head_epoch = head_slot.epoch(T::EthSpec::slots_per_epoch());
            let current_slot = beacon_chain.slot().map_err(|e| {
                error!(
                    log,
                    "Unable to read current slot";
                    "error" => format!("{:?}", e)
                )
            })?;
            let current_epoch = current_slot.epoch(T::EthSpec::slots_per_epoch());
            let finalized_epoch = head.beacon_state.finalized_checkpoint.epoch;
            let finalized_root = head.beacon_state.finalized_checkpoint.root;
            let head_root = head.beacon_block_root;

            let mut speedo = speedo.lock();
            speedo.observe(head_slot, Instant::now());

            // The next two lines take advantage of saturating subtraction on `Slot`.
            let head_distance = current_slot - head_slot;

            if connected_peer_count <= WARN_PEER_COUNT {
                warn!(log, "Low peer count"; "peer_count" => peer_count_pretty(connected_peer_count));
            }

            debug!(
                log,
                "Slot timer";
                "peers" => peer_count_pretty(connected_peer_count),
                "finalized_root" => format!("{}", finalized_root),
                "finalized_epoch" => finalized_epoch,
                "head_block" => format!("{}", head_root),
                "head_slot" => head_slot,
                "current_slot" => current_slot,
            );

            if head_epoch + 1 < current_epoch {
                let distance = format!(
                    "{} slots ({})",
                    head_distance.as_u64(),
                    slot_distance_pretty(head_distance, slot_duration)
                );

                info!(
                    log,
                    "Syncing";
                    "peers" => peer_count_pretty(connected_peer_count),
                    "est_time" => estimated_time_pretty(speedo.estimated_time_till_slot(current_slot)),
                    "speed" => sync_speed_pretty(speedo.slots_per_second()),
                    "distance" => distance
                );

                return Ok(());
            };

            macro_rules! not_quite_synced_log {
                ($message: expr) => {
                    info!(
                        log_2,
                        $message;
                        "peers" => peer_count_pretty(connected_peer_count),
                        "finalized_root" => format!("{}", finalized_root),
                        "finalized_epoch" => finalized_epoch,
                        "head_slot" => head_slot,
                        "current_slot" => current_slot,
                    );
                }
            }

            if head_epoch + 1 == current_epoch {
                not_quite_synced_log!("Synced to previous epoch")
            } else if head_slot != current_slot {
                not_quite_synced_log!("Synced to current epoch")
            } else {
                info!(
                    log_2,
                    "Synced";
                    "peers" => peer_count_pretty(connected_peer_count),
                    "finalized_root" => format!("{}", finalized_root),
                    "finalized_epoch" => finalized_epoch,
                    "epoch" => current_epoch,
                    "slot" => current_slot,
                );
            };

            Ok(())
        });

    let (exit_signal, exit) = exit_future::signal();
    context
        .executor
        .spawn(exit.until(interval_future).map(|_| ()));

    Ok(exit_signal)
}

/// Returns the peer count, returning something helpful if it's `usize::max_value` (effectively a
/// `None` value).
fn peer_count_pretty(peer_count: usize) -> String {
    if peer_count == usize::max_value() {
        String::from("--")
    } else {
        format!("{}", peer_count)
    }
}

/// Returns a nicely formatted string describing the rate of slot imports per second.
fn sync_speed_pretty(slots_per_second: Option<f64>) -> String {
    if let Some(slots_per_second) = slots_per_second {
        format!("{:.2} slots/sec", slots_per_second)
    } else {
        "--".into()
    }
}

/// Returns a nicely formatted string how long will we reach the target slot.
fn estimated_time_pretty(seconds_till_slot: Option<f64>) -> String {
    if let Some(seconds_till_slot) = seconds_till_slot {
        seconds_pretty(seconds_till_slot)
    } else {
        "--".into()
    }
}

/// Returns a nicely formatted string describing the `slot_span` in terms of weeks, days, hours
/// and/or minutes.
fn slot_distance_pretty(slot_span: Slot, slot_duration: Duration) -> String {
    if slot_duration == Duration::from_secs(0) {
        return String::from("Unknown");
    }

    let secs = (slot_duration * slot_span.as_u64() as u32).as_secs();
    seconds_pretty(secs as f64)
}

/// Returns a nicely formatted string describing the `slot_span` in terms of weeks, days, hours
/// and/or minutes.
fn seconds_pretty(secs: f64) -> String {
    if secs <= 0.0 {
        return "--".into();
    }

    let weeks = secs / SECS_PER_WEEK;
    let days = secs / SECS_PER_DAY;
    let hours = secs / SECS_PER_HOUR;
    let minutes = secs / SECS_PER_MINUTE;

    if weeks.floor() > 0.0 {
        format!(
            "{:.0} weeks {:.0} days",
            weeks,
            (days % DAYS_PER_WEEK).round()
        )
    } else if days.floor() > 0.0 {
        format!(
            "{:.0} days {:.0} hrs",
            days,
            (hours % HOURS_PER_DAY).round()
        )
    } else if hours.floor() > 0.0 {
        format!(
            "{:.0} hrs {:.0} mins",
            hours,
            (minutes % MINUTES_PER_HOUR).round()
        )
    } else {
        format!("{:.0} mins", minutes.round())
    }
}

/// "Speedo" is Australian for speedometer. This struct observes syncing times.
#[derive(Default)]
pub struct Speedo(Vec<(Slot, Instant)>);

impl Speedo {
    /// Observe that we were at some `slot` at the given `instant`.
    pub fn observe(&mut self, slot: Slot, instant: Instant) {
        if self.0.len() > SPEEDO_OBSERVATIONS {
            self.0.remove(0);
        }

        self.0.push((slot, instant));
    }

    /// Returns the average of the speeds between each observation.
    ///
    /// Does not gracefully handle slots that are above `u32::max_value()`.
    pub fn slots_per_second(&self) -> Option<f64> {
        let speeds = self
            .0
            .windows(2)
            .filter_map(|windows| {
                let (slot_a, instant_a) = windows[0];
                let (slot_b, instant_b) = windows[1];

                // Taking advantage of saturating subtraction on `Slot`.
                let distance = f64::from((slot_b - slot_a).as_u64() as u32);

                let seconds = f64::from((instant_b - instant_a).as_millis() as u32) / 1_000.0;

                if seconds > 0.0 {
                    Some(distance / seconds)
                } else {
                    None
                }
            })
            .collect::<Vec<f64>>();

        let count = speeds.len();
        let sum: f64 = speeds.iter().sum();

        if count > 0 {
            Some(sum / f64::from(count as u32))
        } else {
            None
        }
    }

    /// Returns the time we should reach the given `slot`, judging by the latest observation and
    /// historical average syncing time.
    ///
    /// Returns `None` if the slot is prior to our latest observed slot or we have not made any
    /// observations.
    pub fn estimated_time_till_slot(&self, target_slot: Slot) -> Option<f64> {
        let (prev_slot, _) = self.0.last()?;
        let slots_per_second = self.slots_per_second()?;

        if target_slot > *prev_slot {
            let distance = (target_slot - *prev_slot).as_u64() as f64;
            Some(distance / slots_per_second)
        } else {
            None
        }
    }
}
<commit_msg>Hopefully fix NaN issue in sync speed (#701)<commit_after>use beacon_chain::{BeaconChain, BeaconChainTypes};
use environment::RuntimeContext;
use exit_future::Signal;
use futures::{Future, Stream};
use network::Service as NetworkService;
use parking_lot::Mutex;
use slog::{debug, error, info, warn};
use slot_clock::SlotClock;
use std::sync::Arc;
use std::time::{Duration, Instant};
use tokio::timer::Interval;
use types::{EthSpec, Slot};

/// Create a warning log whenever the peer count is at or below this value.
pub const WARN_PEER_COUNT: usize = 1;

const SECS_PER_MINUTE: f64 = 60.0;
const SECS_PER_HOUR: f64 = 3600.0;
const SECS_PER_DAY: f64 = 86400.0; // non-leap
const SECS_PER_WEEK: f64 = 604800.0; // non-leap
const DAYS_PER_WEEK: f64 = 7.0;
const HOURS_PER_DAY: f64 = 24.0;
const MINUTES_PER_HOUR: f64 = 60.0;

/// How long to wait for the lock on `network.libp2p_service()` before we give up.
const LIBP2P_LOCK_TIMEOUT: Duration = Duration::from_millis(50);

/// The number of historical observations that should be used to determine the average sync time.
const SPEEDO_OBSERVATIONS: usize = 4;

/// Spawns a notifier service which periodically logs information about the node.
pub fn spawn_notifier<T: BeaconChainTypes>(
    context: RuntimeContext<T::EthSpec>,
    beacon_chain: Arc<BeaconChain<T>>,
    network: Arc<NetworkService<T>>,
    milliseconds_per_slot: u64,
) -> Result<Signal, String> {
    let log_1 = context.log.clone();
    let log_2 = context.log.clone();

    let slot_duration = Duration::from_millis(milliseconds_per_slot);
    let duration_to_next_slot = beacon_chain
        .slot_clock
        .duration_to_next_slot()
        .ok_or_else(|| "slot_notifier unable to determine time to next slot")?;

    // Run this half way through each slot.
    let start_instant = Instant::now() + duration_to_next_slot + (slot_duration / 2);

    // Run this each slot.
    let interval_duration = slot_duration;

    let speedo = Mutex::new(Speedo::default());

    let interval_future = Interval::new(start_instant, interval_duration)
        .map_err(
            move |e| error!(log_1, "Slot notifier timer failed"; "error" => format!("{:?}", e)),
        )
        .for_each(move |_| {
            let log = log_2.clone();

            let connected_peer_count = if let Some(libp2p) = network
                .libp2p_service()
                .try_lock_until(Instant::now() + LIBP2P_LOCK_TIMEOUT)
            {
                libp2p.swarm.connected_peers()
            } else {
                // Use max_value here and we'll print something pretty later.
                usize::max_value()
            };

            let head = beacon_chain.head();

            let head_slot = head.beacon_block.slot;
            let head_epoch = head_slot.epoch(T::EthSpec::slots_per_epoch());
            let current_slot = beacon_chain.slot().map_err(|e| {
                error!(
                    log,
                    "Unable to read current slot";
                    "error" => format!("{:?}", e)
                )
            })?;
            let current_epoch = current_slot.epoch(T::EthSpec::slots_per_epoch());
            let finalized_epoch = head.beacon_state.finalized_checkpoint.epoch;
            let finalized_root = head.beacon_state.finalized_checkpoint.root;
            let head_root = head.beacon_block_root;

            let mut speedo = speedo.lock();
            speedo.observe(head_slot, Instant::now());

            // The next two lines take advantage of saturating subtraction on `Slot`.
            let head_distance = current_slot - head_slot;

            if connected_peer_count <= WARN_PEER_COUNT {
                warn!(log, "Low peer count"; "peer_count" => peer_count_pretty(connected_peer_count));
            }

            debug!(
                log,
                "Slot timer";
                "peers" => peer_count_pretty(connected_peer_count),
                "finalized_root" => format!("{}", finalized_root),
                "finalized_epoch" => finalized_epoch,
                "head_block" => format!("{}", head_root),
                "head_slot" => head_slot,
                "current_slot" => current_slot,
            );

            if head_epoch + 1 < current_epoch {
                let distance = format!(
                    "{} slots ({})",
                    head_distance.as_u64(),
                    slot_distance_pretty(head_distance, slot_duration)
                );

                info!(
                    log,
                    "Syncing";
                    "peers" => peer_count_pretty(connected_peer_count),
                    "est_time" => estimated_time_pretty(speedo.estimated_time_till_slot(current_slot)),
                    "speed" => sync_speed_pretty(speedo.slots_per_second()),
                    "distance" => distance
                );

                return Ok(());
            };

            macro_rules! not_quite_synced_log {
                ($message: expr) => {
                    info!(
                        log_2,
                        $message;
                        "peers" => peer_count_pretty(connected_peer_count),
                        "finalized_root" => format!("{}", finalized_root),
                        "finalized_epoch" => finalized_epoch,
                        "head_slot" => head_slot,
                        "current_slot" => current_slot,
                    );
                }
            }

            if head_epoch + 1 == current_epoch {
                not_quite_synced_log!("Synced to previous epoch")
            } else if head_slot != current_slot {
                not_quite_synced_log!("Synced to current epoch")
            } else {
                info!(
                    log_2,
                    "Synced";
                    "peers" => peer_count_pretty(connected_peer_count),
                    "finalized_root" => format!("{}", finalized_root),
                    "finalized_epoch" => finalized_epoch,
                    "epoch" => current_epoch,
                    "slot" => current_slot,
                );
            };

            Ok(())
        });

    let (exit_signal, exit) = exit_future::signal();
    context
        .executor
        .spawn(exit.until(interval_future).map(|_| ()));

    Ok(exit_signal)
}

/// Returns the peer count, returning something helpful if it's `usize::max_value` (effectively a
/// `None` value).
fn peer_count_pretty(peer_count: usize) -> String {
    if peer_count == usize::max_value() {
        String::from("--")
    } else {
        format!("{}", peer_count)
    }
}

/// Returns a nicely formatted string describing the rate of slot imports per second.
fn sync_speed_pretty(slots_per_second: Option<f64>) -> String {
    if let Some(slots_per_second) = slots_per_second {
        format!("{:.2} slots/sec", slots_per_second)
    } else {
        "--".into()
    }
}

/// Returns a nicely formatted string how long will we reach the target slot.
fn estimated_time_pretty(seconds_till_slot: Option<f64>) -> String {
    if let Some(seconds_till_slot) = seconds_till_slot {
        seconds_pretty(seconds_till_slot)
    } else {
        "--".into()
    }
}

/// Returns a nicely formatted string describing the `slot_span` in terms of weeks, days, hours
/// and/or minutes.
fn slot_distance_pretty(slot_span: Slot, slot_duration: Duration) -> String {
    if slot_duration == Duration::from_secs(0) {
        return String::from("Unknown");
    }

    let secs = (slot_duration * slot_span.as_u64() as u32).as_secs();
    seconds_pretty(secs as f64)
}

/// Returns a nicely formatted string describing the `slot_span` in terms of weeks, days, hours
/// and/or minutes.
fn seconds_pretty(secs: f64) -> String {
    if secs <= 0.0 {
        return "--".into();
    }

    let weeks = secs / SECS_PER_WEEK;
    let days = secs / SECS_PER_DAY;
    let hours = secs / SECS_PER_HOUR;
    let minutes = secs / SECS_PER_MINUTE;

    if weeks.floor() > 0.0 {
        format!(
            "{:.0} weeks {:.0} days",
            weeks,
            (days % DAYS_PER_WEEK).round()
        )
    } else if days.floor() > 0.0 {
        format!(
            "{:.0} days {:.0} hrs",
            days,
            (hours % HOURS_PER_DAY).round()
        )
    } else if hours.floor() > 0.0 {
        format!(
            "{:.0} hrs {:.0} mins",
            hours,
            (minutes % MINUTES_PER_HOUR).round()
        )
    } else {
        format!("{:.0} mins", minutes.round())
    }
}

/// "Speedo" is Australian for speedometer. This struct observes syncing times.
#[derive(Default)]
pub struct Speedo(Vec<(Slot, Instant)>);

impl Speedo {
    /// Observe that we were at some `slot` at the given `instant`.
    pub fn observe(&mut self, slot: Slot, instant: Instant) {
        if self.0.len() > SPEEDO_OBSERVATIONS {
            self.0.remove(0);
        }

        self.0.push((slot, instant));
    }

    /// Returns the average of the speeds between each observation.
    ///
    /// Does not gracefully handle slots that are above `u32::max_value()`.
    pub fn slots_per_second(&self) -> Option<f64> {
        let speeds = self
            .0
            .windows(2)
            .filter_map(|windows| {
                let (slot_a, instant_a) = windows[0];
                let (slot_b, instant_b) = windows[1];

                // Taking advantage of saturating subtraction on `Slot`.
                let distance = f64::from((slot_b - slot_a).as_u64() as u32);

                let seconds = f64::from((instant_b - instant_a).as_millis() as u32) / 1_000.0;

                if seconds > 0.0 {
                    Some(distance / seconds)
                } else {
                    None
                }
            })
            .collect::<Vec<f64>>();

        let count = speeds.len();
        let sum: f64 = speeds.iter().sum();

        if count > 0 {
            Some(sum / f64::from(count as u32))
        } else {
            None
        }
    }

    /// Returns the time we should reach the given `slot`, judging by the latest observation and
    /// historical average syncing time.
    ///
    /// Returns `None` if the slot is prior to our latest observed slot or we have not made any
    /// observations.
    pub fn estimated_time_till_slot(&self, target_slot: Slot) -> Option<f64> {
        let (prev_slot, _) = self.0.last()?;
        let slots_per_second = self.slots_per_second()?;

        if target_slot > *prev_slot && slots_per_second > 0.0 {
            let distance = (target_slot - *prev_slot).as_u64() as f64;
            Some(distance / slots_per_second)
        } else {
            None
        }
    }
}
<|endoftext|>


<commit_before>use fraction::Number;
use square_matrix::SquareMatrix;

#[derive(Debug, Clone, Copy)]
pub struct Point {
    pub x: i64,
    pub y: i64,
}

//
// Singularity
//

impl<T> SquareMatrix<T> where T: Number<T> {
    pub fn is_singular(&self) -> bool {
        self.determinant() == T::zero()
    }
}

//
// Area & volume
//

pub fn triangle_area(a: Point, b: Point, c: Point) -> f64 {
    triangle_area_signed(a, b, c).abs()
}

fn triangle_area_signed(a: Point, b: Point, c: Point) -> f64 {
    let matrix = SquareMatrix::from_vec(&vec![
        a.x, a.y, 1,
        b.x, b.y, 1,
        c.x, c.y, 1,
    ]);

    matrix.determinant() as f64 / 2.
}

// The triangle_area function can be generalized to the volume of a tetrahedron, and further to the
// volume of a simplex defined by d+1 points in d dimensions.
pub fn simplex_volume(points: &Vec<Vec<i64>>) -> f64 {
    simplex_volume_signed(points).abs()
}

fn simplex_volume_signed(points: &Vec<Vec<i64>>) -> f64 {
    let d = points.len() - 1;

    let mut matrix_vec = Vec::with_capacity((d + 1) * (d + 1));
    for point in points {
        matrix_vec.extend_from_slice(&point);
        matrix_vec.push(1);
    }

    let matrix = SquareMatrix::from_vec(&matrix_vec);
    matrix.determinant() as f64 / factorial(d) as f64
}

fn factorial(n: usize) -> usize {
    (2..n+1).fold(1, |f, i| f * i)
}

//
// Collinearity & coplanarity
//

pub fn collinear(a: Point, b: Point, c: Point) -> bool {
    triangle_area(a, b, c) == 0.
}

// Again, the collinear function can be generalized to determine if d+1 points are coplanar in d
// dimensions.
pub fn coplanar(points: &Vec<Vec<i64>>) -> bool {
    simplex_volume(points) == 0.
}

//
// Side of a point relative to a line or plane
//

#[derive(Debug, PartialEq)]
pub enum LineSide {
    Left,
    Right,
    On,
}

// Does c lie to the left, to the right, or on the directed line that passes through a before b?
// Note that "left" and "right" are not relative to the x-axis, but to the direction of the line.
pub fn line_side(a: Point, b: Point, c: Point) -> LineSide {
    let area = triangle_area_signed(a, b, c);
    if area > 0. {
        LineSide::Left
    } else if area < 0. {
        LineSide::Right
    } else {
        LineSide::On
    }
}

#[derive(Debug, PartialEq)]
pub enum PlaneSide {
    A,
    B,
    On,
}

// The line_side function can be generalized to answering if a point lies on one side, on the other
// side, or on an oriented plane defined by d points in d dimensions.
pub fn plane_side(plane: &Vec<Vec<i64>>, point: &Vec<i64>) -> PlaneSide {
    let mut points = plane.clone();
    points.push(point.clone());

    let area = simplex_volume_signed(&points);

    if area > 0. {
        PlaneSide::A
    } else if area < 0. {
        PlaneSide::B
    } else {
        PlaneSide::On
    }
}

//
// Side of a point relative to a circle
//

#[derive(Debug, PartialEq)]
pub enum CircleSide {
    Inside,
    Outside,
    On,
}

pub fn circle_side(circle: [Point; 3], point: Point) -> CircleSide {
    let [c0, c1, c2] = circle;

    let matrix = SquareMatrix::from_vec(&vec![
        c0.x, c0.y, c0.x.pow(2) + c0.y.pow(2), 1,
        c1.x, c1.y, c1.x.pow(2) + c1.y.pow(2), 1,
        c2.x, c2.y, c2.x.pow(2) + c2.y.pow(2), 1,
        point.x, point.y, point.x.pow(2) + point.y.pow(2), 1,
    ]);

    let det = matrix.determinant();

    if det > 0 {
        CircleSide::Inside
    } else if det < 0 {
        CircleSide::Outside
    } else {
        CircleSide::On
    }
}

//
// Intersection between line and line segment
//

pub fn intersects(line: [Point; 2], segment: [Point; 2]) -> bool {
    line_side(line[0], line[1], segment[0]) != line_side(line[0], line[1], segment[1])
}
<commit_msg>[determinant/rust] Replace `if` with `match`<commit_after>use fraction::Number;
use square_matrix::SquareMatrix;

#[derive(Debug, Clone, Copy)]
pub struct Point {
    pub x: i64,
    pub y: i64,
}

//
// Singularity
//

impl<T> SquareMatrix<T> where T: Number<T> {
    pub fn is_singular(&self) -> bool {
        self.determinant() == T::zero()
    }
}

//
// Area & volume
//

pub fn triangle_area(a: Point, b: Point, c: Point) -> f64 {
    triangle_area_signed(a, b, c).abs()
}

fn triangle_area_signed(a: Point, b: Point, c: Point) -> f64 {
    let matrix = SquareMatrix::from_vec(&vec![
        a.x, a.y, 1,
        b.x, b.y, 1,
        c.x, c.y, 1,
    ]);

    matrix.determinant() as f64 / 2.
}

// The triangle_area function can be generalized to the volume of a tetrahedron, and further to the
// volume of a simplex defined by d+1 points in d dimensions.
pub fn simplex_volume(points: &Vec<Vec<i64>>) -> f64 {
    simplex_volume_signed(points).abs()
}

fn simplex_volume_signed(points: &Vec<Vec<i64>>) -> f64 {
    let d = points.len() - 1;

    let mut matrix_vec = Vec::with_capacity((d + 1) * (d + 1));
    for point in points {
        matrix_vec.extend_from_slice(&point);
        matrix_vec.push(1);
    }

    let matrix = SquareMatrix::from_vec(&matrix_vec);
    matrix.determinant() as f64 / factorial(d) as f64
}

fn factorial(n: usize) -> usize {
    (2..n+1).fold(1, |f, i| f * i)
}

//
// Collinearity & coplanarity
//

pub fn collinear(a: Point, b: Point, c: Point) -> bool {
    triangle_area(a, b, c) == 0.
}

// Again, the collinear function can be generalized to determine if d+1 points are coplanar in d
// dimensions.
pub fn coplanar(points: &Vec<Vec<i64>>) -> bool {
    simplex_volume(points) == 0.
}

//
// Side of a point relative to a line or plane
//

#[derive(Debug, PartialEq)]
pub enum LineSide {
    Left,
    Right,
    On,
}

// Does c lie to the left, to the right, or on the directed line that passes through a before b?
// Note that "left" and "right" are not relative to the x-axis, but to the direction of the line.
pub fn line_side(a: Point, b: Point, c: Point) -> LineSide {
    match triangle_area_signed(a, b, c) {
        area if area > 0. => LineSide::Left,
        area if area < 0. => LineSide::Right,
        _ => LineSide::On,
    }
}

#[derive(Debug, PartialEq)]
pub enum PlaneSide {
    A,
    B,
    On,
}

// The line_side function can be generalized to answering if a point lies on one side, on the other
// side, or on an oriented plane defined by d points in d dimensions.
pub fn plane_side(plane: &Vec<Vec<i64>>, point: &Vec<i64>) -> PlaneSide {
    let mut points = plane.clone();
    points.push(point.clone());

    match simplex_volume_signed(&points) {
        area if area > 0. => PlaneSide::A,
        area if area < 0. => PlaneSide::B,
        _ => PlaneSide::On,
    }
}

//
// Side of a point relative to a circle
//

#[derive(Debug, PartialEq)]
pub enum CircleSide {
    Inside,
    Outside,
    On,
}

pub fn circle_side(circle: [Point; 3], point: Point) -> CircleSide {
    let [c0, c1, c2] = circle;

    let matrix = SquareMatrix::from_vec(&vec![
        c0.x, c0.y, c0.x.pow(2) + c0.y.pow(2), 1,
        c1.x, c1.y, c1.x.pow(2) + c1.y.pow(2), 1,
        c2.x, c2.y, c2.x.pow(2) + c2.y.pow(2), 1,
        point.x, point.y, point.x.pow(2) + point.y.pow(2), 1,
    ]);

    match matrix.determinant() {
        d if d > 0 => CircleSide::Inside,
        d if d < 0 => CircleSide::Outside,
        _ => CircleSide::On,
    }
}

//
// Intersection between line and line segment
//

pub fn intersects(line: [Point; 2], segment: [Point; 2]) -> bool {
    line_side(line[0], line[1], segment[0]) != line_side(line[0], line[1], segment[1])
}
<|endoftext|>


<commit_before>//! A post-processing effect to highlight edges.

use na::Vector2;

use context::Context;
use post_processing::post_processing_effect::PostProcessingEffect;
use resource::{
    AllocationType, BufferType, Effect, GPUVec, RenderTarget, ShaderAttribute, ShaderUniform,
};

#[path = "../error.rs"]
mod error;

/// Post processing effect which turns everything in grayscales.
pub struct SobelEdgeHighlight {
    shiftx: f32,
    shifty: f32,
    zn: f32,
    zf: f32,
    threshold: f32,
    shader: Effect,
    gl_nx: ShaderUniform<f32>,
    gl_ny: ShaderUniform<f32>,
    gl_fbo_depth: ShaderUniform<i32>,
    gl_fbo_texture: ShaderUniform<i32>,
    gl_znear: ShaderUniform<f32>,
    gl_zfar: ShaderUniform<f32>,
    gl_threshold: ShaderUniform<f32>,
    gl_v_coord: ShaderAttribute<Vector2<f32>>,
    gl_fbo_vertices: GPUVec<Vector2<f32>>,
}

impl SobelEdgeHighlight {
    /// Creates a new SobelEdgeHighlight post processing effect.
    pub fn new(threshold: f32) -> SobelEdgeHighlight {
        let fbo_vertices: Vec<Vector2<f32>> = vec![
            Vector2::new(-1.0, -1.0),
            Vector2::new(1.0, -1.0),
            Vector2::new(-1.0, 1.0),
            Vector2::new(1.0, 1.0),
        ];

        let mut fbo_vertices =
            GPUVec::new(fbo_vertices, BufferType::Array, AllocationType::StaticDraw);
        fbo_vertices.load_to_gpu();
        fbo_vertices.unload_from_ram();

        let mut shader = Effect::new_from_str(VERTEX_SHADER, FRAGMENT_SHADER);

        shader.use_program();

        SobelEdgeHighlight {
            shiftx: 0.0,
            shifty: 0.0,
            zn: 0.0,
            zf: 0.0,
            threshold: threshold,
            gl_nx: shader.get_uniform("nx").unwrap(),
            gl_ny: shader.get_uniform("ny").unwrap(),
            gl_fbo_depth: shader.get_uniform("fbo_depth").unwrap(),
            gl_fbo_texture: shader.get_uniform("fbo_texture").unwrap(),
            gl_znear: shader.get_uniform("znear").unwrap(),
            gl_zfar: shader.get_uniform("zfar").unwrap(),
            gl_threshold: shader.get_uniform("threshold").unwrap(),
            gl_v_coord: shader.get_attrib("v_coord").unwrap(),
            gl_fbo_vertices: fbo_vertices,
            shader: shader,
        }
    }
}

impl PostProcessingEffect for SobelEdgeHighlight {
    fn update(&mut self, _: f32, w: f32, h: f32, znear: f32, zfar: f32) {
        self.shiftx = 2.0 / w;
        self.shifty = 2.0 / h;
        self.zn = znear;
        self.zf = zfar;
    }

    fn draw(&mut self, target: &RenderTarget) {
        let ctxt = Context::get();
        self.gl_v_coord.enable();

        /*
         * Finalize draw
         */
        verify!(ctxt.clear(Context::COLOR_BUFFER_BIT | Context::DEPTH_BUFFER_BIT));

        self.shader.use_program();

        self.gl_threshold.upload(&self.threshold);
        self.gl_nx.upload(&self.shiftx);
        self.gl_ny.upload(&self.shifty);
        self.gl_znear.upload(&self.zn);
        self.gl_zfar.upload(&self.zf);

        verify!(ctxt.active_texture(Context::TEXTURE0));
        verify!(ctxt.bind_texture(Context::TEXTURE_2D, target.texture_id()));

        self.gl_fbo_texture.upload(&0);

        verify!(ctxt.active_texture(Context::TEXTURE1));
        verify!(ctxt.bind_texture(Context::TEXTURE_2D, target.depth_id()));

        self.gl_fbo_depth.upload(&1);

        self.gl_v_coord.bind(&mut self.gl_fbo_vertices);

        verify!(ctxt.draw_arrays(Context::TRIANGLE_STRIP, 0, 4));

        self.gl_v_coord.disable();
    }
}

static VERTEX_SHADER: &'static str = "#version 100
    attribute vec2    v_coord;
    uniform sampler2D fbo_depth;
    uniform sampler2D fbo_texture;
    uniform float     nx;
    uniform float     ny;
    uniform float     znear;
    uniform float     zfar;
    uniform float     threshold;
    varying vec2      f_texcoord;

    void main(void) {
        gl_Position = vec4(v_coord, 0.0, 1.0);
        f_texcoord  = (v_coord + 1.0) / 2.0;
    }";

static FRAGMENT_SHADER: &'static str = "#version 100
#ifdef GL_FRAGMENT_PRECISION_HIGH
   precision highp float;
#else
   precision mediump float;
#endif

    uniform sampler2D fbo_depth;
    uniform sampler2D fbo_texture;
    uniform float     nx;
    uniform float     ny;
    uniform float     znear;
    uniform float     zfar;
    uniform float     threshold;
    varying vec2      f_texcoord;

    float lin_depth(vec2 uv) {
        float nlin_depth = texture2D(fbo_depth, uv).x;

        return znear * zfar / ((nlin_depth * (zfar - znear)) - zfar);
    }

    void main(void) {
        vec2 texcoord  = f_texcoord;

        float KX[9];
        KX[0] = 1.0; KX[1] = 0.0; KX[2] = -1.0;
        KX[3] = 2.0; KX[4] = 0.0; KX[5] = -2.0;
        KX[6] = 1.0; KX[7] = 0.0; KX[8] = -1.0;

        float gx = 0.0;

        for (int i = -1; i < 2; ++i) {
            for (int j = -1; j < 2; ++j) {
                int off = (i + 1) * 3 + j + 1;
                gx += KX[off] * lin_depth(vec2(f_texcoord.x + float(i) * nx, f_texcoord.y + float(j) * ny));
            }
        }

        float KY[9];
        KY[0] = 1.0;  KY[1] = 2.0;  KY[2] = 1.0;
        KY[3] = 0.0;  KY[4] = 0.0;  KY[5] = 0.0;
        KY[6] = -1.0; KY[7] = -2.0; KY[8] = -1.0;

        float gy = 0.0;

        for (int i = -1; i < 2; ++i) {
            for (int j = -1; j < 2; ++j) {
                int off = (i + 1) * 3 + j + 1;
                gy += KY[off] * lin_depth(vec2(f_texcoord.x + float(i) * nx, f_texcoord.y + float(j) * ny));
            }
        }

        float gradient = sqrt(gx * gx + gy * gy);

        float edge;

        if (gradient > threshold) {
            edge = 0.0;
        }
        else {
            edge = 1.0 - gradient / threshold;
        }

        vec4 color = texture2D(fbo_texture, texcoord);

        gl_FragColor = vec4(edge * color.xyz, 1.0);
    }";
<commit_msg>Fix SobelEdgeHighlight description<commit_after>//! A post-processing effect to highlight edges.

use na::Vector2;

use context::Context;
use post_processing::post_processing_effect::PostProcessingEffect;
use resource::{
    AllocationType, BufferType, Effect, GPUVec, RenderTarget, ShaderAttribute, ShaderUniform,
};

#[path = "../error.rs"]
mod error;

/// Post processing effect which draws detected edges on top of the original buffer.
pub struct SobelEdgeHighlight {
    shiftx: f32,
    shifty: f32,
    zn: f32,
    zf: f32,
    threshold: f32,
    shader: Effect,
    gl_nx: ShaderUniform<f32>,
    gl_ny: ShaderUniform<f32>,
    gl_fbo_depth: ShaderUniform<i32>,
    gl_fbo_texture: ShaderUniform<i32>,
    gl_znear: ShaderUniform<f32>,
    gl_zfar: ShaderUniform<f32>,
    gl_threshold: ShaderUniform<f32>,
    gl_v_coord: ShaderAttribute<Vector2<f32>>,
    gl_fbo_vertices: GPUVec<Vector2<f32>>,
}

impl SobelEdgeHighlight {
    /// Creates a new SobelEdgeHighlight post processing effect.
    pub fn new(threshold: f32) -> SobelEdgeHighlight {
        let fbo_vertices: Vec<Vector2<f32>> = vec![
            Vector2::new(-1.0, -1.0),
            Vector2::new(1.0, -1.0),
            Vector2::new(-1.0, 1.0),
            Vector2::new(1.0, 1.0),
        ];

        let mut fbo_vertices =
            GPUVec::new(fbo_vertices, BufferType::Array, AllocationType::StaticDraw);
        fbo_vertices.load_to_gpu();
        fbo_vertices.unload_from_ram();

        let mut shader = Effect::new_from_str(VERTEX_SHADER, FRAGMENT_SHADER);

        shader.use_program();

        SobelEdgeHighlight {
            shiftx: 0.0,
            shifty: 0.0,
            zn: 0.0,
            zf: 0.0,
            threshold: threshold,
            gl_nx: shader.get_uniform("nx").unwrap(),
            gl_ny: shader.get_uniform("ny").unwrap(),
            gl_fbo_depth: shader.get_uniform("fbo_depth").unwrap(),
            gl_fbo_texture: shader.get_uniform("fbo_texture").unwrap(),
            gl_znear: shader.get_uniform("znear").unwrap(),
            gl_zfar: shader.get_uniform("zfar").unwrap(),
            gl_threshold: shader.get_uniform("threshold").unwrap(),
            gl_v_coord: shader.get_attrib("v_coord").unwrap(),
            gl_fbo_vertices: fbo_vertices,
            shader: shader,
        }
    }
}

impl PostProcessingEffect for SobelEdgeHighlight {
    fn update(&mut self, _: f32, w: f32, h: f32, znear: f32, zfar: f32) {
        self.shiftx = 2.0 / w;
        self.shifty = 2.0 / h;
        self.zn = znear;
        self.zf = zfar;
    }

    fn draw(&mut self, target: &RenderTarget) {
        let ctxt = Context::get();
        self.gl_v_coord.enable();

        /*
         * Finalize draw
         */
        verify!(ctxt.clear(Context::COLOR_BUFFER_BIT | Context::DEPTH_BUFFER_BIT));

        self.shader.use_program();

        self.gl_threshold.upload(&self.threshold);
        self.gl_nx.upload(&self.shiftx);
        self.gl_ny.upload(&self.shifty);
        self.gl_znear.upload(&self.zn);
        self.gl_zfar.upload(&self.zf);

        verify!(ctxt.active_texture(Context::TEXTURE0));
        verify!(ctxt.bind_texture(Context::TEXTURE_2D, target.texture_id()));

        self.gl_fbo_texture.upload(&0);

        verify!(ctxt.active_texture(Context::TEXTURE1));
        verify!(ctxt.bind_texture(Context::TEXTURE_2D, target.depth_id()));

        self.gl_fbo_depth.upload(&1);

        self.gl_v_coord.bind(&mut self.gl_fbo_vertices);

        verify!(ctxt.draw_arrays(Context::TRIANGLE_STRIP, 0, 4));

        self.gl_v_coord.disable();
    }
}

static VERTEX_SHADER: &'static str = "#version 100
    attribute vec2    v_coord;
    uniform sampler2D fbo_depth;
    uniform sampler2D fbo_texture;
    uniform float     nx;
    uniform float     ny;
    uniform float     znear;
    uniform float     zfar;
    uniform float     threshold;
    varying vec2      f_texcoord;

    void main(void) {
        gl_Position = vec4(v_coord, 0.0, 1.0);
        f_texcoord  = (v_coord + 1.0) / 2.0;
    }";

static FRAGMENT_SHADER: &'static str = "#version 100
#ifdef GL_FRAGMENT_PRECISION_HIGH
   precision highp float;
#else
   precision mediump float;
#endif

    uniform sampler2D fbo_depth;
    uniform sampler2D fbo_texture;
    uniform float     nx;
    uniform float     ny;
    uniform float     znear;
    uniform float     zfar;
    uniform float     threshold;
    varying vec2      f_texcoord;

    float lin_depth(vec2 uv) {
        float nlin_depth = texture2D(fbo_depth, uv).x;

        return znear * zfar / ((nlin_depth * (zfar - znear)) - zfar);
    }

    void main(void) {
        vec2 texcoord  = f_texcoord;

        float KX[9];
        KX[0] = 1.0; KX[1] = 0.0; KX[2] = -1.0;
        KX[3] = 2.0; KX[4] = 0.0; KX[5] = -2.0;
        KX[6] = 1.0; KX[7] = 0.0; KX[8] = -1.0;

        float gx = 0.0;

        for (int i = -1; i < 2; ++i) {
            for (int j = -1; j < 2; ++j) {
                int off = (i + 1) * 3 + j + 1;
                gx += KX[off] * lin_depth(vec2(f_texcoord.x + float(i) * nx, f_texcoord.y + float(j) * ny));
            }
        }

        float KY[9];
        KY[0] = 1.0;  KY[1] = 2.0;  KY[2] = 1.0;
        KY[3] = 0.0;  KY[4] = 0.0;  KY[5] = 0.0;
        KY[6] = -1.0; KY[7] = -2.0; KY[8] = -1.0;

        float gy = 0.0;

        for (int i = -1; i < 2; ++i) {
            for (int j = -1; j < 2; ++j) {
                int off = (i + 1) * 3 + j + 1;
                gy += KY[off] * lin_depth(vec2(f_texcoord.x + float(i) * nx, f_texcoord.y + float(j) * ny));
            }
        }

        float gradient = sqrt(gx * gx + gy * gy);

        float edge;

        if (gradient > threshold) {
            edge = 0.0;
        }
        else {
            edge = 1.0 - gradient / threshold;
        }

        vec4 color = texture2D(fbo_texture, texcoord);

        gl_FragColor = vec4(edge * color.xyz, 1.0);
    }";
<|endoftext|>


<commit_before>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */

use crate::cell::ArcRefCell;
use crate::context::LayoutContext;
use crate::dom_traversal::{BoxSlot, Contents, NodeExt, NonReplacedContents, TraversalHandler};
use crate::element_data::LayoutBox;
use crate::flow::inline::TextRun;
use crate::formatting_contexts::{IndependentFormattingContext, IndependentLayout};
use crate::positioned::{AbsolutelyPositionedBox, PositioningContext};
use crate::sizing::{BoxContentSizes, ContentSizes, ContentSizesRequest};
use crate::style_ext::DisplayGeneratingBox;
use crate::ContainingBlock;
use servo_arc::Arc;
use std::borrow::Cow;
use style::properties::ComputedValues;
use style::values::computed::Length;
use style::values::specified::text::TextDecorationLine;
use style::Zero;

// FIXME: `min-width: auto` is not zero: https://drafts.csswg.org/css-flexbox/#min-size-auto

#[derive(Debug, Serialize)]
pub(crate) struct FlexContainer {
    children: Vec<ArcRefCell<FlexLevelBox>>,
}

#[derive(Debug, Serialize)]
pub(crate) enum FlexLevelBox {
    FlexItem(IndependentFormattingContext),
    OutOfFlowAbsolutelyPositionedBox(Arc<AbsolutelyPositionedBox>),
}

impl FlexContainer {
    pub fn construct<'dom>(
        context: &LayoutContext,
        node: impl NodeExt<'dom>,
        style: &Arc<ComputedValues>,
        contents: NonReplacedContents,
        content_sizes: ContentSizesRequest,
        propagated_text_decoration_line: TextDecorationLine,
    ) -> (Self, BoxContentSizes) {
        let text_decoration_line =
            propagated_text_decoration_line | style.clone_text_decoration_line();
        let mut builder = FlexContainerBuilder {
            context,
            node,
            style,
            anonymous_style: None,
            text_decoration_line,
            contiguous_text_runs: Vec::new(),
            children: Vec::new(),
        };
        contents.traverse(context, node, style, &mut builder);
        let content_sizes = content_sizes.compute(|| {
            // FIXME
            ContentSizes::zero()
        });
        (builder.finish(), content_sizes)
    }
}

/// https://drafts.csswg.org/css-flexbox/#flex-items
struct FlexContainerBuilder<'a, Node> {
    context: &'a LayoutContext<'a>,
    node: Node,
    style: &'a Arc<ComputedValues>,
    anonymous_style: Option<Arc<ComputedValues>>,
    text_decoration_line: TextDecorationLine,
    contiguous_text_runs: Vec<TextRun>,
    children: Vec<ArcRefCell<FlexLevelBox>>,
}

impl<'a, 'dom, Node: 'dom> TraversalHandler<'dom, Node> for FlexContainerBuilder<'a, Node>
where
    Node: NodeExt<'dom>,
{
    fn handle_text(
        &mut self,
        node: Node,
        text: Cow<'dom, str>,
        parent_style: Arc<ComputedValues>,
    ) {
        self.contiguous_text_runs.push(TextRun {
            tag: node.as_opaque(),
            parent_style,
            text: text.into(),
        })
    }

    /// Or pseudo-element
    fn handle_element(
        &mut self,
        node: Node,
        style: Arc<ComputedValues>,
        display: DisplayGeneratingBox,
        contents: Contents,
        box_slot: BoxSlot<'dom>,
    ) {
        // FIXME: are text runs considered "contiguous" if they are only separated
        // by an out-of-flow abspos element?
        // (That is, are they wrapped in the same anonymous flex item, or each its own?)
        self.wrap_any_text_in_anonymous_block_container();

        let display_inside = match display {
            DisplayGeneratingBox::OutsideInside { inside, .. } => inside,
        };
        let box_ = if style.get_box().position.is_absolutely_positioned() {
            // https://drafts.csswg.org/css-flexbox/#abspos-items
            ArcRefCell::new(FlexLevelBox::OutOfFlowAbsolutelyPositionedBox(Arc::new(
                AbsolutelyPositionedBox::construct(
                    self.context,
                    node,
                    style.clone(),
                    display_inside,
                    contents,
                ),
            )))
        } else {
            ArcRefCell::new(FlexLevelBox::FlexItem(
                IndependentFormattingContext::construct(
                    self.context,
                    node,
                    style.clone(),
                    display_inside,
                    contents,
                    ContentSizesRequest::None, // FIXME: request sizes when we start using them
                    self.text_decoration_line,
                ),
            ))
        };
        self.children.push(box_.clone());
        box_slot.set(LayoutBox::FlexLevel(box_))
    }
}

/// https://drafts.csswg.org/css-text/#white-space
fn is_only_document_white_space(string: &str) -> bool {
    // FIXME: is this the right definition? See
    // https://github.com/w3c/csswg-drafts/issues/5146
    // https://github.com/w3c/csswg-drafts/issues/5147
    string
        .bytes()
        .all(|byte| matches!(byte, b' ' | b'\n' | b'\t'))
}

impl<'a, 'dom, Node: 'dom> FlexContainerBuilder<'a, Node>
where
    Node: NodeExt<'dom>,
{
    fn wrap_any_text_in_anonymous_block_container(&mut self) {
        if self
            .contiguous_text_runs
            .iter()
            .all(|run| is_only_document_white_space(&run.text))
        {
            // There is no text run, or they all only contain document white space characters
            self.contiguous_text_runs.clear();
            return;
        }
        let context = self.context;
        let style = self.style;
        let anonymous_style = self.anonymous_style.get_or_insert_with(|| {
            context
                .shared_context()
                .stylist
                .style_for_anonymous::<Node::ConcreteElement>(
                    &context.shared_context().guards,
                    &style::selector_parser::PseudoElement::ServoText,
                    style,
                )
        });
        self.children.push(ArcRefCell::new(FlexLevelBox::FlexItem(
            IndependentFormattingContext::construct_for_text_runs(
                self.context,
                self.node,
                anonymous_style.clone(),
                self.contiguous_text_runs.drain(..),
                ContentSizesRequest::None, // FIXME: request sizes when we start using them
                self.text_decoration_line,
            ),
        )))
    }

    fn finish(mut self) -> FlexContainer {
        self.wrap_any_text_in_anonymous_block_container();

        // https://drafts.csswg.org/css-flexbox/#order-modified-document-order
        self.children.sort_by_key(|child| match &*child.borrow() {
            FlexLevelBox::FlexItem(item) => item.style.clone_order(),

            // “Absolutely-positioned children of a flex container are treated
            //  as having order: 0 for the purpose of determining their painting order
            //  relative to flex items.”
            FlexLevelBox::OutOfFlowAbsolutelyPositionedBox(_) => 0,
        });

        FlexContainer {
            children: self.children,
        }
    }
}

impl FlexContainer {
    pub(crate) fn layout(
        &self,
        layout_context: &LayoutContext,
        positioning_context: &mut PositioningContext,
        containing_block: &ContainingBlock,
        tree_rank: usize,
    ) -> IndependentLayout {
        // FIXME
        let _ = layout_context;
        let _ = positioning_context;
        let _ = containing_block;
        let _ = tree_rank;
        IndependentLayout {
            fragments: Vec::new(),
            content_block_size: Length::zero(),
        }
    }
}
<commit_msg>Parallelize flex item boxes construction<commit_after>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */

use crate::cell::ArcRefCell;
use crate::context::LayoutContext;
use crate::dom_traversal::{BoxSlot, Contents, NodeExt, NonReplacedContents, TraversalHandler};
use crate::element_data::LayoutBox;
use crate::formatting_contexts::{IndependentFormattingContext, IndependentLayout};
use crate::positioned::{AbsolutelyPositionedBox, PositioningContext};
use crate::sizing::{BoxContentSizes, ContentSizes, ContentSizesRequest};
use crate::style_ext::DisplayGeneratingBox;
use crate::ContainingBlock;
use rayon::iter::{IntoParallelIterator, ParallelIterator};
use servo_arc::Arc;
use std::borrow::Cow;
use style::properties::ComputedValues;
use style::values::computed::Length;
use style::values::specified::text::TextDecorationLine;
use style::Zero;

// FIXME: `min-width: auto` is not zero: https://drafts.csswg.org/css-flexbox/#min-size-auto

#[derive(Debug, Serialize)]
pub(crate) struct FlexContainer {
    children: Vec<ArcRefCell<FlexLevelBox>>,
}

#[derive(Debug, Serialize)]
pub(crate) enum FlexLevelBox {
    FlexItem(IndependentFormattingContext),
    OutOfFlowAbsolutelyPositionedBox(Arc<AbsolutelyPositionedBox>),
}

impl FlexContainer {
    pub fn construct<'dom>(
        context: &LayoutContext,
        node: impl NodeExt<'dom>,
        style: &Arc<ComputedValues>,
        contents: NonReplacedContents,
        content_sizes: ContentSizesRequest,
        propagated_text_decoration_line: TextDecorationLine,
    ) -> (Self, BoxContentSizes) {
        let text_decoration_line =
            propagated_text_decoration_line | style.clone_text_decoration_line();
        let mut builder = FlexContainerBuilder {
            context,
            node,
            style,
            text_decoration_line,
            contiguous_text_runs: Vec::new(),
            jobs: Vec::new(),
            has_text_runs: false,
        };
        contents.traverse(context, node, style, &mut builder);
        let content_sizes = content_sizes.compute(|| {
            // FIXME
            ContentSizes::zero()
        });
        (builder.finish(), content_sizes)
    }
}

/// https://drafts.csswg.org/css-flexbox/#flex-items
struct FlexContainerBuilder<'a, 'dom, Node> {
    context: &'a LayoutContext<'a>,
    node: Node,
    style: &'a Arc<ComputedValues>,
    text_decoration_line: TextDecorationLine,
    contiguous_text_runs: Vec<TextRun<'dom, Node>>,
    /// To be run in parallel with rayon in `finish`
    jobs: Vec<FlexLevelJob<'dom, Node>>,
    has_text_runs: bool,
}

enum FlexLevelJob<'dom, Node> {
    /// Or pseudo-element
    Element {
        node: Node,
        style: Arc<ComputedValues>,
        display: DisplayGeneratingBox,
        contents: Contents,
        box_slot: BoxSlot<'dom>,
    },
    TextRuns(Vec<TextRun<'dom, Node>>),
}

struct TextRun<'dom, Node> {
    node: Node,
    text: Cow<'dom, str>,
    parent_style: Arc<ComputedValues>,
}

impl<'a, 'dom, Node: 'dom> TraversalHandler<'dom, Node> for FlexContainerBuilder<'a, 'dom, Node>
where
    Node: NodeExt<'dom>,
{
    fn handle_text(&mut self, node: Node, text: Cow<'dom, str>, parent_style: Arc<ComputedValues>) {
        self.contiguous_text_runs.push(TextRun {
            node,
            text,
            parent_style,
        })
    }

    /// Or pseudo-element
    fn handle_element(
        &mut self,
        node: Node,
        style: Arc<ComputedValues>,
        display: DisplayGeneratingBox,
        contents: Contents,
        box_slot: BoxSlot<'dom>,
    ) {
        // FIXME: are text runs considered "contiguous" if they are only separated
        // by an out-of-flow abspos element?
        // (That is, are they wrapped in the same anonymous flex item, or each its own?)
        self.wrap_any_text_in_anonymous_block_container();

        self.jobs.push(FlexLevelJob::Element {
            node,
            style,
            display,
            contents,
            box_slot,
        })
    }
}

/// https://drafts.csswg.org/css-text/#white-space
fn is_only_document_white_space<Node>(run: &TextRun<'_, Node>) -> bool {
    // FIXME: is this the right definition? See
    // https://github.com/w3c/csswg-drafts/issues/5146
    // https://github.com/w3c/csswg-drafts/issues/5147
    run.text
        .bytes()
        .all(|byte| matches!(byte, b' ' | b'\n' | b'\t'))
}

impl<'a, 'dom, Node: 'dom> FlexContainerBuilder<'a, 'dom, Node>
where
    Node: NodeExt<'dom>,
{
    fn wrap_any_text_in_anonymous_block_container(&mut self) {
        let runs = std::mem::take(&mut self.contiguous_text_runs);
        if runs.iter().all(is_only_document_white_space) {
            // There is no text run, or they all only contain document white space characters
        } else {
            self.jobs.push(FlexLevelJob::TextRuns(runs));
            self.has_text_runs = true;
        }
    }

    fn finish(mut self) -> FlexContainer {
        self.wrap_any_text_in_anonymous_block_container();

        let anonymous_style = if self.has_text_runs {
            Some(
                self.context
                    .shared_context()
                    .stylist
                    .style_for_anonymous::<Node::ConcreteElement>(
                        &self.context.shared_context().guards,
                        &style::selector_parser::PseudoElement::ServoText,
                        self.style,
                    ),
            )
        } else {
            None
        };

        let mut children = std::mem::take(&mut self.jobs)
            .into_par_iter()
            .map(|job| match job {
                FlexLevelJob::TextRuns(runs) => ArcRefCell::new(FlexLevelBox::FlexItem(
                    IndependentFormattingContext::construct_for_text_runs(
                        self.context,
                        self.node,
                        anonymous_style.clone().unwrap(),
                        runs.into_iter().map(|run| crate::flow::inline::TextRun {
                            tag: run.node.as_opaque(),
                            text: run.text.into(),
                            parent_style: run.parent_style,
                        }),
                        ContentSizesRequest::None, // FIXME: request sizes when we start using them
                        self.text_decoration_line,
                    ),
                )),
                FlexLevelJob::Element {
                    node,
                    style,
                    display,
                    contents,
                    box_slot,
                } => {
                    let display_inside = match display {
                        DisplayGeneratingBox::OutsideInside { inside, .. } => inside,
                    };
                    let box_ = if style.get_box().position.is_absolutely_positioned() {
                        // https://drafts.csswg.org/css-flexbox/#abspos-items
                        ArcRefCell::new(FlexLevelBox::OutOfFlowAbsolutelyPositionedBox(Arc::new(
                            AbsolutelyPositionedBox::construct(
                                self.context,
                                node,
                                style.clone(),
                                display_inside,
                                contents,
                            ),
                        )))
                    } else {
                        ArcRefCell::new(FlexLevelBox::FlexItem(
                            IndependentFormattingContext::construct(
                                self.context,
                                node,
                                style.clone(),
                                display_inside,
                                contents,
                                ContentSizesRequest::None, // FIXME: request sizes when we start using them
                                self.text_decoration_line,
                            ),
                        ))
                    };
                    box_slot.set(LayoutBox::FlexLevel(box_.clone()));
                    box_
                },
            })
            .collect::<Vec<_>>();

        // https://drafts.csswg.org/css-flexbox/#order-modified-document-order
        children.sort_by_key(|child| match &*child.borrow() {
            FlexLevelBox::FlexItem(item) => item.style.clone_order(),

            // “Absolutely-positioned children of a flex container are treated
            //  as having order: 0 for the purpose of determining their painting order
            //  relative to flex items.”
            FlexLevelBox::OutOfFlowAbsolutelyPositionedBox(_) => 0,
        });

        FlexContainer { children }
    }
}

impl FlexContainer {
    pub(crate) fn layout(
        &self,
        layout_context: &LayoutContext,
        positioning_context: &mut PositioningContext,
        containing_block: &ContainingBlock,
        tree_rank: usize,
    ) -> IndependentLayout {
        // FIXME
        let _ = layout_context;
        let _ = positioning_context;
        let _ = containing_block;
        let _ = tree_rank;
        IndependentLayout {
            fragments: Vec::new(),
            content_block_size: Length::zero(),
        }
    }
}
<|endoftext|>


<commit_before>// Copyright (c) Aptos
// SPDX-License-Identifier: Apache-2.0

use aptos_types::account_address::AccountAddress;
use e2e_move_tests::package_builder::PackageBuilder;
use e2e_move_tests::{assert_abort, assert_success, assert_vm_status, MoveHarness};
use framework::natives::code::{PackageRegistry, UpgradePolicy};
use move_deps::move_core_types::parser::parse_struct_tag;
use move_deps::move_core_types::vm_status::StatusCode;
use serde::{Deserialize, Serialize};

mod common;

/// Mimics `0xcafe::test::State`
#[derive(Serialize, Deserialize)]
struct State {
    value: u64,
}

// TODO: figure reason for flaky access to Move.toml, yielding on CI sometimes to
// thread 'code_publishing_framework_upgrade' panicked at 'building package must succeed:
// Unable to find package manifest in '/runner/_work/aptos-core/aptos-core/aptos-move/e2e-move-tests/tests/code_publishing.data/pack_stdlib'
// or in its parents', aptos-move/e2e-move-tests/src/harness.rs:181:14

#[test]
fn code_publishing_basic() {
    // Parallel execution and code publishing don't work well yet, hence all test harness created
    // here have this off
    let mut h = MoveHarness::new_no_parallel();
    let acc = h.new_account_at(AccountAddress::from_hex_literal("0xcafe").unwrap());
    assert_success!(h.publish_package(
        &acc,
        &common::test_dir_path("code_publishing.data/pack_initial"),
    ));

    // Validate metadata as expected.
    let registry = h
        .read_resource::<PackageRegistry>(
            acc.address(),
            parse_struct_tag("0x1::code::PackageRegistry").unwrap(),
        )
        .unwrap();
    assert_eq!(registry.packages.len(), 1);
    assert_eq!(registry.packages[0].name, "test_package");
    assert_eq!(registry.packages[0].modules.len(), 1);
    assert_eq!(registry.packages[0].modules[0].name, "test");

    // Validate code loaded as expected.
    assert_success!(h.run_entry_function(
        &acc,
        str::parse("0xcafe::test::hello").unwrap(),
        vec![],
        vec![bcs::to_bytes::<u64>(&42).unwrap()]
    ));
    let state = h
        .read_resource::<State>(
            acc.address(),
            parse_struct_tag("0xcafe::test::State").unwrap(),
        )
        .unwrap();
    assert_eq!(state.value, 42)
}

#[test]
fn code_publishing_upgrade_success_no_compat() {
    let mut h = MoveHarness::new_no_parallel();
    let acc = h.new_account_at(AccountAddress::from_hex_literal("0xcafe").unwrap());

    // Install the initial version with no compat requirements
    assert_success!(h.publish_package(
        &acc,
        &common::test_dir_path("code_publishing.data/pack_initial_arbitrary"),
    ));

    // We should be able to upgrade it with the incompatible version
    assert_success!(h.publish_package(
        &acc,
        &common::test_dir_path("code_publishing.data/pack_upgrade_incompat_arbitrary"),
    ));
}

#[test]
fn code_publishing_upgrade_success_compat() {
    let mut h = MoveHarness::new_no_parallel();
    let acc = h.new_account_at(AccountAddress::from_hex_literal("0xcafe").unwrap());

    // Install the initial version with compat requirements
    assert_success!(h.publish_package(
        &acc,
        &common::test_dir_path("code_publishing.data/pack_initial"),
    ));

    // We should be able to upgrade it with the compatible version
    assert_success!(h.publish_package(
        &acc,
        &common::test_dir_path("code_publishing.data/pack_upgrade_compat"),
    ));
}

#[test]
fn code_publishing_upgrade_fail_compat() {
    let mut h = MoveHarness::new_no_parallel();
    let acc = h.new_account_at(AccountAddress::from_hex_literal("0xcafe").unwrap());

    // Install the initial version with compat requirements
    assert_success!(h.publish_package(
        &acc,
        &common::test_dir_path("code_publishing.data/pack_initial"),
    ));

    // We should not be able to upgrade it with the incompatible version
    let status = h.publish_package(
        &acc,
        &common::test_dir_path("code_publishing.data/pack_upgrade_incompat"),
    );
    assert_vm_status!(status, StatusCode::BACKWARD_INCOMPATIBLE_MODULE_UPDATE)
}

#[test]
fn code_publishing_upgrade_fail_immutable() {
    let mut h = MoveHarness::new_no_parallel();
    let acc = h.new_account_at(AccountAddress::from_hex_literal("0xcafe").unwrap());

    // Install the initial version with immutable requirements
    assert_success!(h.publish_package(
        &acc,
        &common::test_dir_path("code_publishing.data/pack_initial_immutable"),
    ));

    // We should not be able to upgrade it with the compatible version
    let status = h.publish_package(
        &acc,
        &common::test_dir_path("code_publishing.data/pack_upgrade_compat"),
    );
    assert_abort!(status, _);
}

#[test]
fn code_publishing_upgrade_fail_overlapping_module() {
    let mut h = MoveHarness::new_no_parallel();
    let acc = h.new_account_at(AccountAddress::from_hex_literal("0xcafe").unwrap());

    // Install the initial version
    assert_success!(h.publish_package(
        &acc,
        &common::test_dir_path("code_publishing.data/pack_initial"),
    ));

    // Install a different package with the same module.
    let status = h.publish_package(
        &acc,
        &common::test_dir_path("code_publishing.data/pack_other_name"),
    );
    assert_abort!(status, _);
}

/// This test verifies that the cache incoherence bug on module upgrade is fixed. This bug
/// exposes itself by that after module upgrade the old version of the module stays
/// active until the MoveVM terminates. In order to workaround this until there is a better
/// fix, we flush the cache in `MoveVmExt::new_session`. One can verify the fix by commenting
/// the flush operation out, then this test fails.
///
/// TODO: for some reason this test did not capture a serious bug in `code::check_coexistence`.
#[test]
fn code_publishing_upgrade_loader_cache_consistency() {
    let mut h = MoveHarness::new_no_parallel();
    let acc = h.new_account_at(AccountAddress::from_hex_literal("0xcafe").unwrap());

    // Create a sequence of package upgrades
    let txns = vec![
        h.create_publish_package(
            &acc,
            &common::test_dir_path("code_publishing.data/pack_initial"),
            None,
        ),
        // Compatible with above package
        h.create_publish_package(
            &acc,
            &common::test_dir_path("code_publishing.data/pack_upgrade_compat"),
            None,
        ),
        // Not compatible with above package, but with first one.
        // Correct behavior: should create backward_incompatible error
        // Bug behavior: succeeds because is compared with the first module
        h.create_publish_package(
            &acc,
            &common::test_dir_path("code_publishing.data/pack_compat_first_not_second"),
            None,
        ),
    ];
    let result = h.run_block(txns);
    assert_success!(result[0]);
    assert_success!(result[1]);
    assert_vm_status!(result[2], StatusCode::BACKWARD_INCOMPATIBLE_MODULE_UPDATE)
}

#[test]
fn code_publishing_framework_upgrade() {
    let mut h = MoveHarness::new_no_parallel();
    let acc = h.aptos_framework_account();

    // We should be able to upgrade move-stdlib, as our local package has only
    // compatible changes. (We added a new function to string.move.)
    assert_success!(h.publish_package(
        &acc,
        &common::test_dir_path("code_publishing.data/pack_stdlib"),
    ));
}

#[test]
fn code_publishing_framework_upgrade_fail() {
    let mut h = MoveHarness::new_no_parallel();
    let acc = h.aptos_framework_account();

    // We should not be able to upgrade move-stdlib because we removed a function
    // from the string module.
    let result = h.publish_package(
        &acc,
        &common::test_dir_path("code_publishing.data/pack_stdlib_incompat"),
    );
    assert_vm_status!(result, StatusCode::BACKWARD_INCOMPATIBLE_MODULE_UPDATE)
}

#[test]
fn code_publishing_weak_dep_fail() {
    let mut h = MoveHarness::new_no_parallel();
    let acc = h.new_account_at(AccountAddress::from_hex_literal("0xcafe").unwrap());

    let mut weak = PackageBuilder::new("WeakPackage").with_policy(UpgradePolicy::arbitrary());
    weak.add_source("weak", "module 0xcafe::weak { public fun f() {} }");

    let weak_dir = weak.write_to_temp().unwrap();
    assert_success!(h.publish_package(&acc, weak_dir.path()));

    let mut normal = PackageBuilder::new("Package").with_policy(UpgradePolicy::compat());
    normal.add_dep(&format!(
        "WeakPackage = {{ local = \"{}\" }}",
        weak_dir.path().display()
    ));
    normal.add_source(
        "normal",
        "module 0xcafe::normal { use 0xcafe::weak; public fun f() { weak::f() } }",
    );
    let normal_dir = normal.write_to_temp().unwrap();
    let status = h.publish_package(&acc, normal_dir.path());
    assert_abort!(status, 0x10006 /*invalid_arhument(EDEP_WEAKER_POLICY)*/);
}

#[test]
fn code_publishing_arbitray_dep_different_address() {
    let mut h = MoveHarness::new_no_parallel();
    let acc1 = h.new_account_at(AccountAddress::from_hex_literal("0xcafe").unwrap());
    let acc2 = h.new_account_at(AccountAddress::from_hex_literal("0xdeaf").unwrap());

    let mut pack1 = PackageBuilder::new("Package1").with_policy(UpgradePolicy::arbitrary());
    pack1.add_source("m", "module 0xcafe::m { public fun f() {} }");
    let pack1_dir = pack1.write_to_temp().unwrap();

    let mut pack2 = PackageBuilder::new("Package2").with_policy(UpgradePolicy::arbitrary());
    pack2.add_dep(&format!(
        "Package1 = {{ local = \"{}\" }}",
        pack1_dir.path().display()
    ));
    pack2.add_source(
        "m",
        "module 0xdeaf::m { use 0xcafe::m; public fun f() { m::f() } }",
    );
    let pack2_dir = pack2.write_to_temp().unwrap();

    assert_success!(h.publish_package(&acc1, pack1_dir.path()));
    assert_abort!(
        h.publish_package(&acc2, pack2_dir.path()),
        0x10007 /*EDEP_ARBITRARY_NOT_SAME_ADDRESS*/
    );
}
<commit_msg>[move-tests] move-packaging should support parallel execution now<commit_after>// Copyright (c) Aptos
// SPDX-License-Identifier: Apache-2.0

use aptos_types::account_address::AccountAddress;
use e2e_move_tests::package_builder::PackageBuilder;
use e2e_move_tests::{assert_abort, assert_success, assert_vm_status, MoveHarness};
use framework::natives::code::{PackageRegistry, UpgradePolicy};
use move_deps::move_core_types::parser::parse_struct_tag;
use move_deps::move_core_types::vm_status::StatusCode;
use serde::{Deserialize, Serialize};

mod common;

/// Mimics `0xcafe::test::State`
#[derive(Serialize, Deserialize)]
struct State {
    value: u64,
}

// TODO: figure reason for flaky access to Move.toml, yielding on CI sometimes to
// thread 'code_publishing_framework_upgrade' panicked at 'building package must succeed:
// Unable to find package manifest in '/runner/_work/aptos-core/aptos-core/aptos-move/e2e-move-tests/tests/code_publishing.data/pack_stdlib'
// or in its parents', aptos-move/e2e-move-tests/src/harness.rs:181:14

#[test]
fn code_publishing_basic() {
    let mut h = MoveHarness::new();
    let acc = h.new_account_at(AccountAddress::from_hex_literal("0xcafe").unwrap());
    assert_success!(h.publish_package(
        &acc,
        &common::test_dir_path("code_publishing.data/pack_initial"),
    ));

    // Validate metadata as expected.
    let registry = h
        .read_resource::<PackageRegistry>(
            acc.address(),
            parse_struct_tag("0x1::code::PackageRegistry").unwrap(),
        )
        .unwrap();
    assert_eq!(registry.packages.len(), 1);
    assert_eq!(registry.packages[0].name, "test_package");
    assert_eq!(registry.packages[0].modules.len(), 1);
    assert_eq!(registry.packages[0].modules[0].name, "test");

    // Validate code loaded as expected.
    assert_success!(h.run_entry_function(
        &acc,
        str::parse("0xcafe::test::hello").unwrap(),
        vec![],
        vec![bcs::to_bytes::<u64>(&42).unwrap()]
    ));
    let state = h
        .read_resource::<State>(
            acc.address(),
            parse_struct_tag("0xcafe::test::State").unwrap(),
        )
        .unwrap();
    assert_eq!(state.value, 42)
}

#[test]
fn code_publishing_upgrade_success_no_compat() {
    let mut h = MoveHarness::new();
    let acc = h.new_account_at(AccountAddress::from_hex_literal("0xcafe").unwrap());

    // Install the initial version with no compat requirements
    assert_success!(h.publish_package(
        &acc,
        &common::test_dir_path("code_publishing.data/pack_initial_arbitrary"),
    ));

    // We should be able to upgrade it with the incompatible version
    assert_success!(h.publish_package(
        &acc,
        &common::test_dir_path("code_publishing.data/pack_upgrade_incompat_arbitrary"),
    ));
}

#[test]
fn code_publishing_upgrade_success_compat() {
    let mut h = MoveHarness::new();
    let acc = h.new_account_at(AccountAddress::from_hex_literal("0xcafe").unwrap());

    // Install the initial version with compat requirements
    assert_success!(h.publish_package(
        &acc,
        &common::test_dir_path("code_publishing.data/pack_initial"),
    ));

    // We should be able to upgrade it with the compatible version
    assert_success!(h.publish_package(
        &acc,
        &common::test_dir_path("code_publishing.data/pack_upgrade_compat"),
    ));
}

#[test]
fn code_publishing_upgrade_fail_compat() {
    let mut h = MoveHarness::new();
    let acc = h.new_account_at(AccountAddress::from_hex_literal("0xcafe").unwrap());

    // Install the initial version with compat requirements
    assert_success!(h.publish_package(
        &acc,
        &common::test_dir_path("code_publishing.data/pack_initial"),
    ));

    // We should not be able to upgrade it with the incompatible version
    let status = h.publish_package(
        &acc,
        &common::test_dir_path("code_publishing.data/pack_upgrade_incompat"),
    );
    assert_vm_status!(status, StatusCode::BACKWARD_INCOMPATIBLE_MODULE_UPDATE)
}

#[test]
fn code_publishing_upgrade_fail_immutable() {
    let mut h = MoveHarness::new();
    let acc = h.new_account_at(AccountAddress::from_hex_literal("0xcafe").unwrap());

    // Install the initial version with immutable requirements
    assert_success!(h.publish_package(
        &acc,
        &common::test_dir_path("code_publishing.data/pack_initial_immutable"),
    ));

    // We should not be able to upgrade it with the compatible version
    let status = h.publish_package(
        &acc,
        &common::test_dir_path("code_publishing.data/pack_upgrade_compat"),
    );
    assert_abort!(status, _);
}

#[test]
fn code_publishing_upgrade_fail_overlapping_module() {
    let mut h = MoveHarness::new();
    let acc = h.new_account_at(AccountAddress::from_hex_literal("0xcafe").unwrap());

    // Install the initial version
    assert_success!(h.publish_package(
        &acc,
        &common::test_dir_path("code_publishing.data/pack_initial"),
    ));

    // Install a different package with the same module.
    let status = h.publish_package(
        &acc,
        &common::test_dir_path("code_publishing.data/pack_other_name"),
    );
    assert_abort!(status, _);
}

/// This test verifies that the cache incoherence bug on module upgrade is fixed. This bug
/// exposes itself by that after module upgrade the old version of the module stays
/// active until the MoveVM terminates. In order to workaround this until there is a better
/// fix, we flush the cache in `MoveVmExt::new_session`. One can verify the fix by commenting
/// the flush operation out, then this test fails.
///
/// TODO: for some reason this test did not capture a serious bug in `code::check_coexistence`.
#[test]
fn code_publishing_upgrade_loader_cache_consistency() {
    let mut h = MoveHarness::new();
    let acc = h.new_account_at(AccountAddress::from_hex_literal("0xcafe").unwrap());

    // Create a sequence of package upgrades
    let txns = vec![
        h.create_publish_package(
            &acc,
            &common::test_dir_path("code_publishing.data/pack_initial"),
            None,
        ),
        // Compatible with above package
        h.create_publish_package(
            &acc,
            &common::test_dir_path("code_publishing.data/pack_upgrade_compat"),
            None,
        ),
        // Not compatible with above package, but with first one.
        // Correct behavior: should create backward_incompatible error
        // Bug behavior: succeeds because is compared with the first module
        h.create_publish_package(
            &acc,
            &common::test_dir_path("code_publishing.data/pack_compat_first_not_second"),
            None,
        ),
    ];
    let result = h.run_block(txns);
    assert_success!(result[0]);
    assert_success!(result[1]);
    assert_vm_status!(result[2], StatusCode::BACKWARD_INCOMPATIBLE_MODULE_UPDATE)
}

#[test]
fn code_publishing_framework_upgrade() {
    let mut h = MoveHarness::new();
    let acc = h.aptos_framework_account();

    // We should be able to upgrade move-stdlib, as our local package has only
    // compatible changes. (We added a new function to string.move.)
    assert_success!(h.publish_package(
        &acc,
        &common::test_dir_path("code_publishing.data/pack_stdlib"),
    ));
}

#[test]
fn code_publishing_framework_upgrade_fail() {
    let mut h = MoveHarness::new();
    let acc = h.aptos_framework_account();

    // We should not be able to upgrade move-stdlib because we removed a function
    // from the string module.
    let result = h.publish_package(
        &acc,
        &common::test_dir_path("code_publishing.data/pack_stdlib_incompat"),
    );
    assert_vm_status!(result, StatusCode::BACKWARD_INCOMPATIBLE_MODULE_UPDATE)
}

#[test]
fn code_publishing_weak_dep_fail() {
    let mut h = MoveHarness::new();
    let acc = h.new_account_at(AccountAddress::from_hex_literal("0xcafe").unwrap());

    let mut weak = PackageBuilder::new("WeakPackage").with_policy(UpgradePolicy::arbitrary());
    weak.add_source("weak", "module 0xcafe::weak { public fun f() {} }");

    let weak_dir = weak.write_to_temp().unwrap();
    assert_success!(h.publish_package(&acc, weak_dir.path()));

    let mut normal = PackageBuilder::new("Package").with_policy(UpgradePolicy::compat());
    normal.add_dep(&format!(
        "WeakPackage = {{ local = \"{}\" }}",
        weak_dir.path().display()
    ));
    normal.add_source(
        "normal",
        "module 0xcafe::normal { use 0xcafe::weak; public fun f() { weak::f() } }",
    );
    let normal_dir = normal.write_to_temp().unwrap();
    let status = h.publish_package(&acc, normal_dir.path());
    assert_abort!(status, 0x10006 /*invalid_arhument(EDEP_WEAKER_POLICY)*/);
}

#[test]
fn code_publishing_arbitray_dep_different_address() {
    let mut h = MoveHarness::new();
    let acc1 = h.new_account_at(AccountAddress::from_hex_literal("0xcafe").unwrap());
    let acc2 = h.new_account_at(AccountAddress::from_hex_literal("0xdeaf").unwrap());

    let mut pack1 = PackageBuilder::new("Package1").with_policy(UpgradePolicy::arbitrary());
    pack1.add_source("m", "module 0xcafe::m { public fun f() {} }");
    let pack1_dir = pack1.write_to_temp().unwrap();

    let mut pack2 = PackageBuilder::new("Package2").with_policy(UpgradePolicy::arbitrary());
    pack2.add_dep(&format!(
        "Package1 = {{ local = \"{}\" }}",
        pack1_dir.path().display()
    ));
    pack2.add_source(
        "m",
        "module 0xdeaf::m { use 0xcafe::m; public fun f() { m::f() } }",
    );
    let pack2_dir = pack2.write_to_temp().unwrap();

    assert_success!(h.publish_package(&acc1, pack1_dir.path()));
    assert_abort!(
        h.publish_package(&acc2, pack2_dir.path()),
        0x10007 /*EDEP_ARBITRARY_NOT_SAME_ADDRESS*/
    );
}
<|endoftext|>


<commit_before>/*
 * Lets talk about header data first.
 * We need:
 *
 * - tags
 * - name (not unique)
 *
 * So an header could look like this:
 *
 * ```json
 *  {
 *   'name': "kittennotes",
 *   'tags': ['foo', 'bar', 'baz'],
 *  }
 * ```
 *
 * Nothing more is required for the header, I guess
 *
 */

use module::helpers;
use module::helpers::header as headerhelpers;
use storage::file::header::data::FileHeaderData as FHD;
use storage::file::header::spec::FileHeaderSpec as FHS;

pub fn get_spec() -> FHS {
    FHS::Map { keys: vec![ helpers::spec::named_text("NAME"),
                           headerhelpers::tags::spec::tags_key() ] }
}

pub fn build_header(name: String, tags: Vec<String>) -> FHD {
    FHD::Map {
        keys: vec![
            FHD::Key {
                name: String::from("NAME"),
                value: Box::new(FHD::Text(name.clone()))
            },
            FHD::Key {
                name: String::from("TAGS"),
                value: Box::new(headerhelpers::tags::data::build_tag_array(tags))
            }
        ]
    }
}

pub fn get_tags_from_header(header: &FHD) -> Vec<String> {
    headerhelpers::tags::data::get_tags_from_header(header)
}

<commit_msg>Notes: Add helper to get name from header<commit_after>/*
 * Lets talk about header data first.
 * We need:
 *
 * - tags
 * - name (not unique)
 *
 * So an header could look like this:
 *
 * ```json
 *  {
 *   'name': "kittennotes",
 *   'tags': ['foo', 'bar', 'baz'],
 *  }
 * ```
 *
 * Nothing more is required for the header, I guess
 *
 */

use module::helpers;
use module::helpers::header as headerhelpers;
use storage::file::header::data::FileHeaderData as FHD;
use storage::file::header::spec::FileHeaderSpec as FHS;

pub fn get_spec() -> FHS {
    FHS::Map { keys: vec![ helpers::spec::named_text("NAME"),
                           headerhelpers::tags::spec::tags_key() ] }
}

pub fn build_header(name: String, tags: Vec<String>) -> FHD {
    FHD::Map {
        keys: vec![
            FHD::Key {
                name: String::from("NAME"),
                value: Box::new(FHD::Text(name.clone()))
            },
            FHD::Key {
                name: String::from("TAGS"),
                value: Box::new(headerhelpers::tags::data::build_tag_array(tags))
            }
        ]
    }
}

pub fn get_tags_from_header(header: &FHD) -> Vec<String> {
    headerhelpers::tags::data::get_tags_from_header(header)
}

/**
 * Get the name from the Header
 *
 * Returns empty string if there is no NAME field
 */
pub fn get_name_from_header(header: &FHD) -> String {
    headerhelpers::data::get_name_from_header(header).unwrap_or(String::from(""))
}

<|endoftext|>