<commit_before>// Copyright (c) 2014 Robert Clipsham <robert@octarineparrot.com>
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

extern crate libc;

use std::clone::Clone;
use std::result::{Ok, Err};
use std::option::{Some};
use std::str::StrSlice;
use std::task::try_future;
use std::io::net::ip::{IpAddr, Ipv4Addr, Ipv6Addr};
use std::iter::Iterator;

use datalink::{datalink_channel};
use packet::Packet;
use packet::ethernet::{EtherTypes, EthernetHeader, MutableEthernetHeader, EthernetPacket};
use packet::ip::{IpNextHeaderProtocols, IpNextHeaderProtocol};
use packet::ipv4::{Ipv4Header, MutableIpv4Header, Ipv4Packet};
use packet::ipv6::{MutableIpv6Header, Ipv6Packet};
use packet::udp::{UdpHeader, MutableUdpHeader, UdpPacket};
use transport::{udp_header_iter, ipv4_header_iter, transport_channel, TransportProtocol, Ipv4, Ipv6};
use transport;
use datalink;
use util::NetworkInterface;

const MIN_PACKET_SIZE: uint = 64;
const ETHERNET_HEADER_LEN: uint = 14;
const IPV4_HEADER_LEN: uint = 20;
const IPV6_HEADER_LEN: uint = 40;
const UDP_HEADER_LEN: uint = 8;
const TEST_DATA_LEN: uint = 4;

const IPV4_SOURCE: IpAddr = Ipv4Addr(127, 0, 0, 1);
const IPV4_DESTINATION: IpAddr = Ipv4Addr(127, 0, 0, 1);
const IPV6_SOURCE: IpAddr = Ipv6Addr(0, 0, 0, 0, 0, 0, 0, 1);
const IPV6_DESTINATION: IpAddr = Ipv6Addr(0, 0, 0, 0, 0, 0, 0, 1);

// Use a protocol which is unlikely to have other packets on
const TEST_PROTO: IpNextHeaderProtocol = IpNextHeaderProtocols::Test1;

fn build_ipv4_header(packet: &mut [u8], offset: uint) {
    let mut ip_header = MutableIpv4Header::new(packet.slice_from_mut(offset));

    let total_len = (IPV4_HEADER_LEN + UDP_HEADER_LEN + TEST_DATA_LEN) as u16;

    ip_header.set_version(4);
    ip_header.set_header_length(5);
    ip_header.set_total_length(total_len);
    ip_header.set_ttl(4);
    ip_header.set_next_level_protocol(TEST_PROTO);
    ip_header.set_source(IPV4_SOURCE);
    ip_header.set_destination(IPV4_DESTINATION);
    ip_header.checksum();
}

fn build_ipv6_header(packet: &mut [u8], offset: uint) {
    let mut ip_header = MutableIpv6Header::new(packet.slice_from_mut(offset));

    ip_header.set_version(6);
    ip_header.set_payload_length((UDP_HEADER_LEN + TEST_DATA_LEN) as u16);
    ip_header.set_next_header(TEST_PROTO);
    ip_header.set_hop_limit(4);
    ip_header.set_source(IPV6_SOURCE);
    ip_header.set_destination(IPV6_DESTINATION);
}

fn build_udp_header(packet: &mut [u8], offset: uint) {
    let mut udp_header = MutableUdpHeader::new(packet.slice_from_mut(offset));

    udp_header.set_source(1234); // Arbitary port number
    udp_header.set_destination(1234);
    udp_header.set_length((UDP_HEADER_LEN + TEST_DATA_LEN) as u16);
}

fn build_udp4_packet(packet: &mut [u8], start: uint, msg: &str) {
    build_ipv4_header(packet, start);
    build_udp_header(packet, start + IPV4_HEADER_LEN as uint);

    let data_start = start + IPV4_HEADER_LEN + UDP_HEADER_LEN;
    packet[data_start + 0] = msg.char_at(0) as u8;
    packet[data_start + 1] = msg.char_at(1) as u8;
    packet[data_start + 2] = msg.char_at(2) as u8;
    packet[data_start + 3] = msg.char_at(3) as u8;

    let slice = packet.slice_from_mut(start + IPV4_HEADER_LEN as uint);
    MutableUdpHeader::new(slice).checksum(IPV4_SOURCE, IPV4_DESTINATION, TEST_PROTO);
}

fn build_udp6_packet(packet: &mut [u8], start: uint, msg: &str) {
    build_ipv6_header(packet, start);
    build_udp_header(packet, start + IPV6_HEADER_LEN as uint);

    let data_start = start + IPV6_HEADER_LEN + UDP_HEADER_LEN;
    packet[data_start + 0] = msg.char_at(0) as u8;
    packet[data_start + 1] = msg.char_at(1) as u8;
    packet[data_start + 2] = msg.char_at(2) as u8;
    packet[data_start + 3] = msg.char_at(3) as u8;

    let slice = packet.slice_from_mut(start + IPV6_HEADER_LEN as uint);
    MutableUdpHeader::new(slice).checksum(IPV6_SOURCE, IPV6_DESTINATION, TEST_PROTO);
}

fn get_test_interface() -> NetworkInterface {
    use std::os::getenv;
    use util;

    (*util::get_network_interfaces()
        .as_slice().iter()
        .filter(|x| {
            match getenv("PNET_TEST_IFACE") {
                Some(name) => x.name == name,
                None => x.is_loopback()
            }
        })
        .next()
        .unwrap())
        .clone()
}

// OSes have a nasty habit of tweaking IP fields, so we only check
// the less volatile fields (identification, checksum)
fn check_ipv4_header(packet: &[u8], header: Ipv4Header) {
    let ipv4_header = Ipv4Header::new(packet);

    assert_eq!(header.get_version(), ipv4_header.get_version());
    assert_eq!(header.get_header_length(), ipv4_header.get_header_length());
    assert_eq!(header.get_dscp(), ipv4_header.get_dscp());
    assert_eq!(header.get_ecn(), ipv4_header.get_ecn());
    assert_eq!(header.get_total_length(), ipv4_header.get_total_length());
    assert_eq!(header.get_flags(), ipv4_header.get_flags());
    assert_eq!(header.get_fragment_offset(), ipv4_header.get_fragment_offset());
    assert_eq!(header.get_ttl(), ipv4_header.get_ttl());
    assert_eq!(header.get_next_level_protocol(), ipv4_header.get_next_level_protocol());
    assert_eq!(header.get_source(), ipv4_header.get_source());
    assert_eq!(header.get_destination(), ipv4_header.get_destination());
}

fn layer4(ip: IpAddr, header_len: uint) {
    let mut packet = [0u8, ..IPV6_HEADER_LEN + UDP_HEADER_LEN + TEST_DATA_LEN];
    let packet_len = header_len + UDP_HEADER_LEN + TEST_DATA_LEN;

    match ip {
        Ipv4Addr(..) => {
            build_udp4_packet(packet.as_mut_slice(), 0, "l4i4")
        },
        Ipv6Addr(..) => {
            build_udp6_packet(packet.as_mut_slice(), 0, "l4i6")
        }
    };

    let udp = UdpHeader::new(packet.slice(header_len, packet_len));

    let (tx, rx) = channel();

    let (mut ttx, mut trx) = match transport_channel(128, transport::Layer4(get_proto(ip))) {
        Ok((tx, rx)) => (tx, rx),
        Err(e) => panic!("layer4: unable to create channel: {}", e),
    };

    let res = try_future( proc() {
        tx.send(());
        pfor!((header, addr) in udp_header_iter(&mut trx) {
            assert_eq!(addr, ip);
            assert_eq!(header, UdpHeader::new(packet.slice(header_len, packet_len)));
            break;
        } on Err(e) {
            panic!("Receive failed for layer4_test(): {}", e);
        })
    });

    rx.recv();
    match ttx.send_to(udp, ip) {
        Ok(res) => assert_eq!(res as uint, UDP_HEADER_LEN + TEST_DATA_LEN),
        Err(e) => panic!("layer4_test failed: {}", e)
    }

    fn get_proto(ip: IpAddr) -> TransportProtocol {
        match ip {
            Ipv4Addr(..) => Ipv4(TEST_PROTO),
            Ipv6Addr(..) => Ipv6(TEST_PROTO)
        }
    }

    match res.unwrap() {
        Err(e) => panic!(e),
        _ => ()
    }
}

#[test]
fn layer4_ipv4() {
    layer4(Ipv4Addr(127, 0, 0, 1), IPV4_HEADER_LEN as uint);
}

#[test]
fn layer4_ipv6() {
    layer4(Ipv6Addr(0, 0, 0, 0, 0, 0, 0, 1), IPV6_HEADER_LEN);
}

#[test]
fn layer3_ipv4() {
    let send_addr = Ipv4Addr(127, 0, 0, 1);
    let mut packet = [0u8, ..IPV4_HEADER_LEN + UDP_HEADER_LEN + TEST_DATA_LEN];

    build_udp4_packet(packet.as_mut_slice(), 0, "l3i4");

    let (tx, rx) = channel();

    let tc =  transport_channel(IPV4_HEADER_LEN + UDP_HEADER_LEN + TEST_DATA_LEN,
                                    transport::Layer3(TEST_PROTO));
    let (mut ttx, mut trx) = match tc {
        Ok((tx, rx)) => (tx, rx),
        Err(e) => panic!("layer3: unable to create channel: {}", e),
    };

    let res = try_future( proc() {
        tx.send(());
        pfor!((header, addr) in ipv4_header_iter(&mut trx) {
            assert_eq!(addr, send_addr);
            check_ipv4_header(packet, header);
            let udp_header = UdpHeader::new(header.packet().slice_from(
                                           header.get_header_length() as uint * 4u));
            assert_eq!(udp_header, UdpHeader::new(packet.slice_from(IPV4_HEADER_LEN)));

            assert_eq!(udp_header.packet().slice_from(UDP_HEADER_LEN),
                       packet.slice_from(IPV4_HEADER_LEN + UDP_HEADER_LEN))
            break;
        } on Err(e) {
            panic!("receive failed for layer3_ipv4_test(): {}", e);
        })
    });

    rx.recv();
    match ttx.send_to(Ipv4Header::new(packet), send_addr) {
        Ok(res) => assert_eq!(res as uint, packet.len()),
        Err(e) => panic!("layer3_ipv4_test failed: {}", e)
    }

    match res.unwrap() {
        Err(e) => panic!(e),
        _ => ()
    }

}

#[test]
fn layer2() {
    let interface = get_test_interface();

    let mut packet = [0u8, ..ETHERNET_HEADER_LEN +
                             IPV4_HEADER_LEN +
                             UDP_HEADER_LEN +
                             TEST_DATA_LEN];

    {
        let mut ethernet_header = MutableEthernetHeader::new(packet.as_mut_slice());
        ethernet_header.set_source(interface.mac_address());
        ethernet_header.set_destination(interface.mac_address());
        ethernet_header.set_ethertype(EtherTypes::Ipv4);
    }

    build_udp4_packet(packet.as_mut_slice(), ETHERNET_HEADER_LEN as uint, "l2tt");

    let (tx, rx) = channel();

    let dlc = datalink_channel(&interface, MIN_PACKET_SIZE*2, MIN_PACKET_SIZE*2, datalink::Layer2);
    let (mut dltx, mut dlrx) = match dlc {
        Ok((tx, rx)) => (tx, rx),
        Err(e) => panic!("layer2: unable to create channel: {}", e)
    };

    let res = try_future( proc() {
        tx.send(());
        let mut i = 0u;
        pfor!(eh in dlrx.iter() {
            if i == 10_000 {
                panic!("layer2: did not find matching packet after 10_000 iterations");
            }
            if EthernetHeader::new(packet) == eh {
                return;
            }
            i += 1;
        } on Err(e) {
            panic!("layer2 failed: {}", e);
        })
    });

    rx.recv();
    match dltx.send_to(EthernetHeader::new(packet), None) {
        Some(Ok(())) => (),
        Some(Err(e)) => panic!("layer2_test failed: {}", e),
        None => panic!("Provided buffer too small")
    }

    match res.unwrap() {
        Err(e) => panic!(e),
        _ => ()
    }
}

#[test]
fn check_test_environment() {
    use std::os;
    let tasks = os::getenv("RUST_TEST_TASKS");
    if !tasks.is_some() || tasks.unwrap().as_slice() != "1" {
        panic!("Tests must be run with environment variable RUST_TEST_TASKS=1");
    }

    test_iface();

    #[cfg(not(target_os = "linux"))]
    fn test_iface() {
        let iface = os::getenv("PNET_TEST_IFACE");
        if !iface.is_some() {
            panic!("The environment variable PNET_TEST_IFACE must be set.");
        }
    }

    #[cfg(target_os = "linux")]
    fn test_iface() {}
}
<commit_msg>Remove import which was causing a build failure for tests.<commit_after>// Copyright (c) 2014 Robert Clipsham <robert@octarineparrot.com>
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

extern crate libc;

use std::clone::Clone;
use std::result::{Ok, Err};
use std::option::{Some};
use std::task::try_future;
use std::io::net::ip::{IpAddr, Ipv4Addr, Ipv6Addr};
use std::iter::Iterator;

use datalink::{datalink_channel};
use packet::Packet;
use packet::ethernet::{EtherTypes, EthernetHeader, MutableEthernetHeader, EthernetPacket};
use packet::ip::{IpNextHeaderProtocols, IpNextHeaderProtocol};
use packet::ipv4::{Ipv4Header, MutableIpv4Header, Ipv4Packet};
use packet::ipv6::{MutableIpv6Header, Ipv6Packet};
use packet::udp::{UdpHeader, MutableUdpHeader, UdpPacket};
use transport::{udp_header_iter, ipv4_header_iter, transport_channel, TransportProtocol, Ipv4, Ipv6};
use transport;
use datalink;
use util::NetworkInterface;

const MIN_PACKET_SIZE: uint = 64;
const ETHERNET_HEADER_LEN: uint = 14;
const IPV4_HEADER_LEN: uint = 20;
const IPV6_HEADER_LEN: uint = 40;
const UDP_HEADER_LEN: uint = 8;
const TEST_DATA_LEN: uint = 4;

const IPV4_SOURCE: IpAddr = Ipv4Addr(127, 0, 0, 1);
const IPV4_DESTINATION: IpAddr = Ipv4Addr(127, 0, 0, 1);
const IPV6_SOURCE: IpAddr = Ipv6Addr(0, 0, 0, 0, 0, 0, 0, 1);
const IPV6_DESTINATION: IpAddr = Ipv6Addr(0, 0, 0, 0, 0, 0, 0, 1);

// Use a protocol which is unlikely to have other packets on
const TEST_PROTO: IpNextHeaderProtocol = IpNextHeaderProtocols::Test1;

fn build_ipv4_header(packet: &mut [u8], offset: uint) {
    let mut ip_header = MutableIpv4Header::new(packet.slice_from_mut(offset));

    let total_len = (IPV4_HEADER_LEN + UDP_HEADER_LEN + TEST_DATA_LEN) as u16;

    ip_header.set_version(4);
    ip_header.set_header_length(5);
    ip_header.set_total_length(total_len);
    ip_header.set_ttl(4);
    ip_header.set_next_level_protocol(TEST_PROTO);
    ip_header.set_source(IPV4_SOURCE);
    ip_header.set_destination(IPV4_DESTINATION);
    ip_header.checksum();
}

fn build_ipv6_header(packet: &mut [u8], offset: uint) {
    let mut ip_header = MutableIpv6Header::new(packet.slice_from_mut(offset));

    ip_header.set_version(6);
    ip_header.set_payload_length((UDP_HEADER_LEN + TEST_DATA_LEN) as u16);
    ip_header.set_next_header(TEST_PROTO);
    ip_header.set_hop_limit(4);
    ip_header.set_source(IPV6_SOURCE);
    ip_header.set_destination(IPV6_DESTINATION);
}

fn build_udp_header(packet: &mut [u8], offset: uint) {
    let mut udp_header = MutableUdpHeader::new(packet.slice_from_mut(offset));

    udp_header.set_source(1234); // Arbitary port number
    udp_header.set_destination(1234);
    udp_header.set_length((UDP_HEADER_LEN + TEST_DATA_LEN) as u16);
}

fn build_udp4_packet(packet: &mut [u8], start: uint, msg: &str) {
    build_ipv4_header(packet, start);
    build_udp_header(packet, start + IPV4_HEADER_LEN as uint);

    let data_start = start + IPV4_HEADER_LEN + UDP_HEADER_LEN;
    packet[data_start + 0] = msg.char_at(0) as u8;
    packet[data_start + 1] = msg.char_at(1) as u8;
    packet[data_start + 2] = msg.char_at(2) as u8;
    packet[data_start + 3] = msg.char_at(3) as u8;

    let slice = packet.slice_from_mut(start + IPV4_HEADER_LEN as uint);
    MutableUdpHeader::new(slice).checksum(IPV4_SOURCE, IPV4_DESTINATION, TEST_PROTO);
}

fn build_udp6_packet(packet: &mut [u8], start: uint, msg: &str) {
    build_ipv6_header(packet, start);
    build_udp_header(packet, start + IPV6_HEADER_LEN as uint);

    let data_start = start + IPV6_HEADER_LEN + UDP_HEADER_LEN;
    packet[data_start + 0] = msg.char_at(0) as u8;
    packet[data_start + 1] = msg.char_at(1) as u8;
    packet[data_start + 2] = msg.char_at(2) as u8;
    packet[data_start + 3] = msg.char_at(3) as u8;

    let slice = packet.slice_from_mut(start + IPV6_HEADER_LEN as uint);
    MutableUdpHeader::new(slice).checksum(IPV6_SOURCE, IPV6_DESTINATION, TEST_PROTO);
}

fn get_test_interface() -> NetworkInterface {
    use std::os::getenv;
    use util;

    (*util::get_network_interfaces()
        .as_slice().iter()
        .filter(|x| {
            match getenv("PNET_TEST_IFACE") {
                Some(name) => x.name == name,
                None => x.is_loopback()
            }
        })
        .next()
        .unwrap())
        .clone()
}

// OSes have a nasty habit of tweaking IP fields, so we only check
// the less volatile fields (identification, checksum)
fn check_ipv4_header(packet: &[u8], header: Ipv4Header) {
    let ipv4_header = Ipv4Header::new(packet);

    assert_eq!(header.get_version(), ipv4_header.get_version());
    assert_eq!(header.get_header_length(), ipv4_header.get_header_length());
    assert_eq!(header.get_dscp(), ipv4_header.get_dscp());
    assert_eq!(header.get_ecn(), ipv4_header.get_ecn());
    assert_eq!(header.get_total_length(), ipv4_header.get_total_length());
    assert_eq!(header.get_flags(), ipv4_header.get_flags());
    assert_eq!(header.get_fragment_offset(), ipv4_header.get_fragment_offset());
    assert_eq!(header.get_ttl(), ipv4_header.get_ttl());
    assert_eq!(header.get_next_level_protocol(), ipv4_header.get_next_level_protocol());
    assert_eq!(header.get_source(), ipv4_header.get_source());
    assert_eq!(header.get_destination(), ipv4_header.get_destination());
}

fn layer4(ip: IpAddr, header_len: uint) {
    let mut packet = [0u8, ..IPV6_HEADER_LEN + UDP_HEADER_LEN + TEST_DATA_LEN];
    let packet_len = header_len + UDP_HEADER_LEN + TEST_DATA_LEN;

    match ip {
        Ipv4Addr(..) => {
            build_udp4_packet(packet.as_mut_slice(), 0, "l4i4")
        },
        Ipv6Addr(..) => {
            build_udp6_packet(packet.as_mut_slice(), 0, "l4i6")
        }
    };

    let udp = UdpHeader::new(packet.slice(header_len, packet_len));

    let (tx, rx) = channel();

    let (mut ttx, mut trx) = match transport_channel(128, transport::Layer4(get_proto(ip))) {
        Ok((tx, rx)) => (tx, rx),
        Err(e) => panic!("layer4: unable to create channel: {}", e),
    };

    let res = try_future( proc() {
        tx.send(());
        pfor!((header, addr) in udp_header_iter(&mut trx) {
            assert_eq!(addr, ip);
            assert_eq!(header, UdpHeader::new(packet.slice(header_len, packet_len)));
            break;
        } on Err(e) {
            panic!("Receive failed for layer4_test(): {}", e);
        })
    });

    rx.recv();
    match ttx.send_to(udp, ip) {
        Ok(res) => assert_eq!(res as uint, UDP_HEADER_LEN + TEST_DATA_LEN),
        Err(e) => panic!("layer4_test failed: {}", e)
    }

    fn get_proto(ip: IpAddr) -> TransportProtocol {
        match ip {
            Ipv4Addr(..) => Ipv4(TEST_PROTO),
            Ipv6Addr(..) => Ipv6(TEST_PROTO)
        }
    }

    match res.unwrap() {
        Err(e) => panic!(e),
        _ => ()
    }
}

#[test]
fn layer4_ipv4() {
    layer4(Ipv4Addr(127, 0, 0, 1), IPV4_HEADER_LEN as uint);
}

#[test]
fn layer4_ipv6() {
    layer4(Ipv6Addr(0, 0, 0, 0, 0, 0, 0, 1), IPV6_HEADER_LEN);
}

#[test]
fn layer3_ipv4() {
    let send_addr = Ipv4Addr(127, 0, 0, 1);
    let mut packet = [0u8, ..IPV4_HEADER_LEN + UDP_HEADER_LEN + TEST_DATA_LEN];

    build_udp4_packet(packet.as_mut_slice(), 0, "l3i4");

    let (tx, rx) = channel();

    let tc =  transport_channel(IPV4_HEADER_LEN + UDP_HEADER_LEN + TEST_DATA_LEN,
                                    transport::Layer3(TEST_PROTO));
    let (mut ttx, mut trx) = match tc {
        Ok((tx, rx)) => (tx, rx),
        Err(e) => panic!("layer3: unable to create channel: {}", e),
    };

    let res = try_future( proc() {
        tx.send(());
        pfor!((header, addr) in ipv4_header_iter(&mut trx) {
            assert_eq!(addr, send_addr);
            check_ipv4_header(packet, header);
            let udp_header = UdpHeader::new(header.packet().slice_from(
                                           header.get_header_length() as uint * 4u));
            assert_eq!(udp_header, UdpHeader::new(packet.slice_from(IPV4_HEADER_LEN)));

            assert_eq!(udp_header.packet().slice_from(UDP_HEADER_LEN),
                       packet.slice_from(IPV4_HEADER_LEN + UDP_HEADER_LEN))
            break;
        } on Err(e) {
            panic!("receive failed for layer3_ipv4_test(): {}", e);
        })
    });

    rx.recv();
    match ttx.send_to(Ipv4Header::new(packet), send_addr) {
        Ok(res) => assert_eq!(res as uint, packet.len()),
        Err(e) => panic!("layer3_ipv4_test failed: {}", e)
    }

    match res.unwrap() {
        Err(e) => panic!(e),
        _ => ()
    }

}

#[test]
fn layer2() {
    let interface = get_test_interface();

    let mut packet = [0u8, ..ETHERNET_HEADER_LEN +
                             IPV4_HEADER_LEN +
                             UDP_HEADER_LEN +
                             TEST_DATA_LEN];

    {
        let mut ethernet_header = MutableEthernetHeader::new(packet.as_mut_slice());
        ethernet_header.set_source(interface.mac_address());
        ethernet_header.set_destination(interface.mac_address());
        ethernet_header.set_ethertype(EtherTypes::Ipv4);
    }

    build_udp4_packet(packet.as_mut_slice(), ETHERNET_HEADER_LEN as uint, "l2tt");

    let (tx, rx) = channel();

    let dlc = datalink_channel(&interface, MIN_PACKET_SIZE*2, MIN_PACKET_SIZE*2, datalink::Layer2);
    let (mut dltx, mut dlrx) = match dlc {
        Ok((tx, rx)) => (tx, rx),
        Err(e) => panic!("layer2: unable to create channel: {}", e)
    };

    let res = try_future( proc() {
        tx.send(());
        let mut i = 0u;
        pfor!(eh in dlrx.iter() {
            if i == 10_000 {
                panic!("layer2: did not find matching packet after 10_000 iterations");
            }
            if EthernetHeader::new(packet) == eh {
                return;
            }
            i += 1;
        } on Err(e) {
            panic!("layer2 failed: {}", e);
        })
    });

    rx.recv();
    match dltx.send_to(EthernetHeader::new(packet), None) {
        Some(Ok(())) => (),
        Some(Err(e)) => panic!("layer2_test failed: {}", e),
        None => panic!("Provided buffer too small")
    }

    match res.unwrap() {
        Err(e) => panic!(e),
        _ => ()
    }
}

#[test]
fn check_test_environment() {
    use std::os;
    let tasks = os::getenv("RUST_TEST_TASKS");
    if !tasks.is_some() || tasks.unwrap().as_slice() != "1" {
        panic!("Tests must be run with environment variable RUST_TEST_TASKS=1");
    }

    test_iface();

    #[cfg(not(target_os = "linux"))]
    fn test_iface() {
        let iface = os::getenv("PNET_TEST_IFACE");
        if !iface.is_some() {
            panic!("The environment variable PNET_TEST_IFACE must be set.");
        }
    }

    #[cfg(target_os = "linux")]
    fn test_iface() {}
}
<|endoftext|>


<commit_before>use std;
use std::sync::{Arc, Mutex, Condvar};
use {ffi, FswSession, FswStatus, FswError, FswMonitorType, FswMonitorFilter, FswFilterType,
     FswEventFlag, FswEvent};

fn get_default_session() -> FswSession {
  FswSession::default().unwrap()
}

fn create_sample_filter() -> FswMonitorFilter {
  FswMonitorFilter::new("\\w+\\.txt$", FswFilterType::Include, false, false)
}

#[test]
fn create_and_destroy_session() {
  let handle = {
    let session = get_default_session();
    session.handle
  };
  // Check that the handle was created successfully.
  assert!(handle != ffi::FSW_INVALID_HANDLE);
  // Check that trying to destroy the handle after the session wrapper goes out of scope fails.
  // This should fail because the wrapper going out of scope should automatically destroy the
  // session.
  assert!(unsafe { ffi::fsw_destroy_session(handle) } != ffi::FSW_OK);
}

#[test]
fn create_session_from_builder() {
  FswSession::builder()
    .add_path("./")
    .property("test_name", "test_value")
    .overflow(Some(true))
    .monitor(FswMonitorType::SystemDefault)
    .latency(Some(1.0))
    .recursive(Some(true))
    .directory_only(Some(false))
    .follow_symlinks(Some(true))
    .add_event_filter(FswEventFlag::Created)
    .add_filter(create_sample_filter())
    .build_callback(|events| println!("{:#?}", events))
    .unwrap();
}

#[test]
fn start_empty() {
  assert_eq!(Err(FswError::MissingRequiredParameters), get_default_session().start_monitor());
}

#[test]
fn start_without_callback() {
  let session = get_default_session();
  session.add_path("./").unwrap();
  assert_eq!(Err(FswError::MissingRequiredParameters), session.start_monitor());
}

#[test]
fn start_without_path() {
  let session = get_default_session();
  session.set_callback(|_| println!("Hello")).unwrap();
  assert_eq!(Err(FswError::MissingRequiredParameters), session.start_monitor());
}

#[test]
fn invalid_path() {
  let session = get_default_session();
  unsafe {
    assert!(ffi::fsw_add_path(session.handle, ::std::ptr::null()) == ffi::FSW_ERR_INVALID_PATH);
  }
}

#[test]
fn invalid_handle_add_path() {
  let mut session = get_default_session();
  // Set handle to the invalid handle before trying to call methods.
  session.handle = ffi::FSW_INVALID_HANDLE;
  let res = session.add_path("./");
  let expected_error = Err(FswError::FromFsw(FswStatus::SessionUnknown));
  assert_eq!(expected_error, res);
  assert!(!session.path_added.get());
}

#[test]
fn invalid_handle_set_callback() {
  let mut session = get_default_session();
  // Set handle to the invalid handle before trying to call methods.
  session.handle = ffi::FSW_INVALID_HANDLE;
  let res = session.set_callback(|_| {});
  let expected_error = Err(FswError::FromFsw(FswStatus::SessionUnknown));
  assert_eq!(expected_error, res);
  assert!(!session.callback_set.get());
}

#[test]
fn run_callback() {
  // Define the file name for this test.
  let file_name = "fsw_test_file";

  // Create new condvar for waiting.
  let pair = Arc::new((Mutex::new(false), Condvar::new()));
  // Create clone for thread.
  let pair2 = pair.clone();

  // Get a handle to this thread.
  let handle = std::thread::spawn(move || {
    // Extract our pair.
    let &(ref lock, ref cvar) = &*pair2;
    // Create a session.
    let session = FswSession::builder_paths(vec!["./"])
      // Filter for only our file name.
      .add_filter(FswMonitorFilter::new(file_name, FswFilterType::Include, true, false))
      // Reject all other files.
      .add_filter(FswMonitorFilter::new(".*", FswFilterType::Exclude, false, false))
      .build().unwrap();
    // Use the iterator pattern but immediately break out of it. This will leave the monitor running
    // and accumulating events (as the C API provides no way to stop a monitor).
    for event in session {
      // Once we get an event, notify our waiting condvar and return the event.
      let mut started = lock.lock().unwrap();
      *started = true;
      cvar.notify_one();
      return event;
    }
    // This should be unreachable code, so panic if we get here.
    unreachable!();
  });

  // Create the file for the thread to find the event for.
  let file_path = format!("./{}", file_name);
  std::fs::File::create(&file_path).unwrap();

  // Wait for the thread for up to five seconds.
  let &(ref lock, ref cvar) = &*pair;
  let mut started = lock.lock().unwrap();
  while !*started {
    let (s, timeout) = cvar.wait_timeout(started, std::time::Duration::from_secs(5)).unwrap();
    started = s;
    // Assert that we didn't time out waiting. This prevents the test from infinitely blocking.
    assert!(!timeout.timed_out());
  }

  // Get the event from the thread.
  let event: FswEvent = handle.join().unwrap();

  // Remove the file.
  std::fs::remove_file(&file_path).unwrap();

  let path = std::path::PathBuf::from(event.path);
  let event_file_name = path.file_name().unwrap().to_string_lossy();
  // Assert that the created file name matches the event's file name.
  assert_eq!(file_name, event_file_name);
  // Assert that the IsFile flag is present in the event flags. Note that we cannot assert that the
  // Created flag is present due to how fswatch works.
  assert!(event.flags.contains(&FswEventFlag::IsFile));
}
<commit_msg>fix: make the callback test work on linux<commit_after>use std;
use std::sync::{Arc, Mutex, Condvar};
use {ffi, FswSession, FswStatus, FswError, FswMonitorType, FswMonitorFilter, FswFilterType,
     FswEventFlag, FswEvent};

fn get_default_session() -> FswSession {
  FswSession::default().unwrap()
}

fn create_sample_filter() -> FswMonitorFilter {
  FswMonitorFilter::new("\\w+\\.txt$", FswFilterType::Include, false, false)
}

#[test]
fn create_and_destroy_session() {
  let handle = {
    let session = get_default_session();
    session.handle
  };
  // Check that the handle was created successfully.
  assert!(handle != ffi::FSW_INVALID_HANDLE);
  // Check that trying to destroy the handle after the session wrapper goes out of scope fails.
  // This should fail because the wrapper going out of scope should automatically destroy the
  // session.
  assert!(unsafe { ffi::fsw_destroy_session(handle) } != ffi::FSW_OK);
}

#[test]
fn create_session_from_builder() {
  FswSession::builder()
    .add_path("./")
    .property("test_name", "test_value")
    .overflow(Some(true))
    .monitor(FswMonitorType::SystemDefault)
    .latency(Some(1.0))
    .recursive(Some(true))
    .directory_only(Some(false))
    .follow_symlinks(Some(true))
    .add_event_filter(FswEventFlag::Created)
    .add_filter(create_sample_filter())
    .build_callback(|events| println!("{:#?}", events))
    .unwrap();
}

#[test]
fn start_empty() {
  assert_eq!(Err(FswError::MissingRequiredParameters), get_default_session().start_monitor());
}

#[test]
fn start_without_callback() {
  let session = get_default_session();
  session.add_path("./").unwrap();
  assert_eq!(Err(FswError::MissingRequiredParameters), session.start_monitor());
}

#[test]
fn start_without_path() {
  let session = get_default_session();
  session.set_callback(|_| println!("Hello")).unwrap();
  assert_eq!(Err(FswError::MissingRequiredParameters), session.start_monitor());
}

#[test]
fn invalid_path() {
  let session = get_default_session();
  unsafe {
    assert!(ffi::fsw_add_path(session.handle, ::std::ptr::null()) == ffi::FSW_ERR_INVALID_PATH);
  }
}

#[test]
fn invalid_handle_add_path() {
  let mut session = get_default_session();
  // Set handle to the invalid handle before trying to call methods.
  session.handle = ffi::FSW_INVALID_HANDLE;
  let res = session.add_path("./");
  let expected_error = Err(FswError::FromFsw(FswStatus::SessionUnknown));
  assert_eq!(expected_error, res);
  assert!(!session.path_added.get());
}

#[test]
fn invalid_handle_set_callback() {
  let mut session = get_default_session();
  // Set handle to the invalid handle before trying to call methods.
  session.handle = ffi::FSW_INVALID_HANDLE;
  let res = session.set_callback(|_| {});
  let expected_error = Err(FswError::FromFsw(FswStatus::SessionUnknown));
  assert_eq!(expected_error, res);
  assert!(!session.callback_set.get());
}

#[test]
fn run_callback() {
  // Get the cwd.
  let dir = std::env::current_dir().unwrap();
  // Define the file name for this test.
  let file_name = "fsw_test_file";

  // Create new condvar for waiting.
  let pair = Arc::new((Mutex::new(false), Condvar::new()));
  // Create clone for thread.
  let pair2 = pair.clone();
  // Create clone for thread.
  let dir2 = dir.clone();

  let (tx, rx) = std::sync::mpsc::channel();

  // Get a handle to this thread.
  let handle = std::thread::spawn(move || {
    // Extract our pair.
    let &(ref lock, ref cvar) = &*pair2;
    // Create a session.
    let session = FswSession::builder_paths(vec![dir2])
      // Filter for only our file name.
      .add_filter(FswMonitorFilter::new(file_name, FswFilterType::Include, true, false))
      // Reject all other files.
      .add_filter(FswMonitorFilter::new(".*", FswFilterType::Exclude, false, false))
      .build().unwrap();
    // Send a signal to the main thread that we're ready for the file to be created.
    tx.send(()).unwrap();
    // Use the iterator pattern but immediately break out of it. This will leave the monitor running
    // and accumulating events (as the C API provides no way to stop a monitor).
    for event in session {
      // Once we get an event, notify our waiting condvar and return the event.
      let mut started = lock.lock().unwrap();
      *started = true;
      cvar.notify_one();
      return event;
    }
    // This should be unreachable code, so panic if we get here.
    unreachable!();
  });

  // Generate the path for the file to create.
  let mut file_path = dir.clone();
  file_path.push(file_name);

  // Wait for the signal before creating the file.
  let _ = rx.recv().unwrap();

  // Wait one second for the loop to begin. // FIXME: there is a better way for this
  std::thread::sleep(std::time::Duration::from_secs(1));

  // Create the file for the thread to find the event for.
  std::fs::File::create(&file_path).unwrap();

  // Wait for the thread for up to five seconds.
  let &(ref lock, ref cvar) = &*pair;
  let mut started = lock.lock().unwrap();
  while !*started {
    let (s, timeout) = cvar.wait_timeout(started, std::time::Duration::from_secs(5)).unwrap();
    started = s;
    // Assert that we didn't time out waiting. This prevents the test from infinitely blocking.
    assert!(!timeout.timed_out());
  }

  // Get the event from the thread.
  let event: FswEvent = handle.join().unwrap();

  // Remove the file.
  std::fs::remove_file(&file_path).unwrap();

  let path = std::path::PathBuf::from(event.path);
  let event_file_name = path.file_name().unwrap().to_string_lossy();
  // Assert that the created file name matches the event's file name.
  assert_eq!(file_name, event_file_name);
}
<|endoftext|>


<commit_before>use std::{
    fmt::{self, Display, Formatter},
    ops::Add,
};

use im::HashMap;
use itertools::Itertools;
use linked_hash_map::LinkedHashMap;

use crate::input::Input;

use super::ast_to_hir::AstToHir;

#[salsa::query_group(HirDbStorage)]
pub trait HirDb: AstToHir {
    fn find_expression(&self, id: Id) -> Option<Expression>;
    fn all_hir_ids(&self, input: Input) -> Option<Vec<Id>>;
}
fn find_expression(db: &dyn HirDb, id: Id) -> Option<Expression> {
    let (hir, _) = db.hir(id.input.clone()).unwrap();
    if id.is_root() {
        return Some(Expression::Body(hir.as_ref().to_owned()));
    }

    hir.find(&id).map(|it| it.to_owned())
}
fn all_hir_ids(db: &dyn HirDb, input: Input) -> Option<Vec<Id>> {
    let (hir, _) = db.hir(input)?;
    let mut ids = vec![];
    hir.collect_all_ids(&mut ids);
    log::info!("all HIR IDs: {:?}", ids);
    Some(ids)
}

impl Expression {
    fn collect_all_ids(&self, ids: &mut Vec<Id>) {
        match self {
            Expression::Int(_) => {}
            Expression::Text(_) => {}
            Expression::Reference(_) => {}
            Expression::Symbol(_) => {}
            Expression::Struct(entries) => {
                for (key_id, value_id) in entries.iter() {
                    ids.push(key_id.to_owned());
                    ids.push(value_id.to_owned());
                }
            }
            Expression::Lambda(Lambda { body, .. }) => {
                // TODO: list parameter IDs?
                // for (index, _) in parameters.iter().enumerate() {
                //     ids.push(first_id.to_owned() + index);
                // }
                body.collect_all_ids(ids);
            }
            Expression::Body(body) => body.collect_all_ids(ids),
            Expression::Call { arguments, .. } => {
                ids.extend(arguments.iter().cloned());
            }
            Expression::Error => {}
        }
    }
}
impl Body {
    fn collect_all_ids(&self, ids: &mut Vec<Id>) {
        ids.extend(self.expressions.keys().into_iter().cloned());
        for expression in self.expressions.values() {
            expression.collect_all_ids(ids);
        }
    }
}

#[derive(Debug, PartialEq, Eq, Clone, Hash, PartialOrd, Ord)]
pub struct Id {
    pub input: Input,
    pub local: Vec<usize>,
}
impl Id {
    pub fn new(input: Input, local: Vec<usize>) -> Self {
        Self { input, local }
    }

    pub fn root(input: Input) -> Self {
        Id {
            input,
            local: vec![],
        }
    }
    pub fn is_root(&self) -> bool {
        self.local.is_empty()
    }

    pub fn parent(&self) -> Option<Id> {
        match self.local.len() {
            0 => None,
            _ => Some(Id {
                input: self.input.clone(),
                local: self.local[..self.local.len() - 1].to_vec(),
            }),
        }
    }
}
impl Add<usize> for Id {
    type Output = Self;

    fn add(self, rhs: usize) -> Self::Output {
        let mut local = self.local[..self.local.len() - 1].to_vec();
        local.push(self.local.last().unwrap() + rhs);
        Id {
            input: self.input,
            local: local,
        }
    }
}
impl Display for Id {
    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
        write!(f, "HirId({:?}:{:?})", self.input, self.local)
    }
}

#[derive(Clone, PartialEq, Eq, Hash, Debug)]
pub enum Expression {
    Int(u64),
    Text(String),
    Reference(Id),
    Symbol(String),
    Struct(HashMap<Id, Id>),
    Lambda(Lambda),
    Body(Body),
    Call { function: Id, arguments: Vec<Id> },
    Error,
}
impl Expression {
    pub fn nothing() -> Self {
        Expression::Symbol("Nothing".to_owned())
    }
}

#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Lambda {
    pub first_id: Id,
    pub parameters: Vec<String>,
    pub body: Body,
}

#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Body {
    pub expressions: LinkedHashMap<Id, Expression>,
    pub identifiers: HashMap<Id, String>,
}

impl Body {
    pub fn new() -> Self {
        Self {
            expressions: LinkedHashMap::new(),
            identifiers: HashMap::new(),
        }
    }
    pub fn push(&mut self, id: Id, expression: Expression, identifier: Option<String>) {
        self.expressions.insert(id.to_owned(), expression);
        if let Some(identifier) = identifier {
            self.identifiers.insert(id, identifier);
        }
    }
    pub fn out_id(&self) -> &Id {
        self.expressions.keys().last().unwrap()
    }
}

impl fmt::Display for Expression {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Expression::Int(int) => write!(f, "int {}", int),
            Expression::Text(text) => write!(f, "text {:?}", text),
            Expression::Reference(reference) => write!(f, "reference {}", reference),
            Expression::Symbol(symbol) => write!(f, "symbol {}", symbol),
            Expression::Struct(entries) => {
                write!(
                    f,
                    "struct [\n{}\n]",
                    entries
                        .iter()
                        .map(|(id, value)| format!("  {}: {}", id, value))
                        .join(",\n"),
                )
            }
            Expression::Lambda(lambda) => {
                write!(
                    f,
                    "lambda [\n{}\n]",
                    lambda
                        .to_string()
                        .lines()
                        .map(|line| format!("  {}", line))
                        .join("\n"),
                )
            }
            Expression::Body(body) => {
                write!(
                    f,
                    "body [\n{}\n]",
                    body.to_string()
                        .lines()
                        .map(|line| format!("  {}", line))
                        .join("\n"),
                )
            }
            Expression::Call {
                function,
                arguments,
            } => {
                assert!(arguments.len() > 0, "A call needs to have arguments.");
                write!(f, "call {} with {}", function, arguments.iter().join(" "))
            }
            Expression::Error => write!(f, "<error>"),
        }
    }
}
impl fmt::Display for Lambda {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{} parameters\n", self.parameters.len())?;
        write!(f, "{}", self.body)?;
        Ok(())
    }
}
impl fmt::Display for Body {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        for (id, expression) in self.expressions.iter() {
            write!(f, "{} = {}\n", id, expression)?;
        }
        Ok(())
    }
}

impl Expression {
    fn find(&self, id: &Id) -> Option<&Self> {
        match self {
            Expression::Int { .. } => None,
            Expression::Text { .. } => None,
            Expression::Reference { .. } => None,
            Expression::Symbol { .. } => None,
            Expression::Struct(_) => None,
            Expression::Lambda(Lambda { body, .. }) => body.find(id),
            Expression::Body(body) => body.find(id),
            Expression::Call { .. } => None,
            Expression::Error { .. } => None,
        }
    }
}
impl Body {
    fn find(&self, id: &Id) -> Option<&Expression> {
        if let Some(expression) = self.expressions.get(id) {
            Some(expression)
        } else {
            self.expressions
                .iter()
                .filter(|(key, _)| key <= &id)
                .max_by_key(|(key, _)| key.local.to_owned())?
                .1
                .find(id)
        }
    }
}
<commit_msg>refactor(compiler): remove unused hir::Id::root(…)<commit_after>use std::{
    fmt::{self, Display, Formatter},
    ops::Add,
};

use im::HashMap;
use itertools::Itertools;
use linked_hash_map::LinkedHashMap;

use crate::input::Input;

use super::ast_to_hir::AstToHir;

#[salsa::query_group(HirDbStorage)]
pub trait HirDb: AstToHir {
    fn find_expression(&self, id: Id) -> Option<Expression>;
    fn all_hir_ids(&self, input: Input) -> Option<Vec<Id>>;
}
fn find_expression(db: &dyn HirDb, id: Id) -> Option<Expression> {
    let (hir, _) = db.hir(id.input.clone()).unwrap();
    if id.is_root() {
        return Some(Expression::Body(hir.as_ref().to_owned()));
    }

    hir.find(&id).map(|it| it.to_owned())
}
fn all_hir_ids(db: &dyn HirDb, input: Input) -> Option<Vec<Id>> {
    let (hir, _) = db.hir(input)?;
    let mut ids = vec![];
    hir.collect_all_ids(&mut ids);
    log::info!("all HIR IDs: {:?}", ids);
    Some(ids)
}

impl Expression {
    fn collect_all_ids(&self, ids: &mut Vec<Id>) {
        match self {
            Expression::Int(_) => {}
            Expression::Text(_) => {}
            Expression::Reference(_) => {}
            Expression::Symbol(_) => {}
            Expression::Struct(entries) => {
                for (key_id, value_id) in entries.iter() {
                    ids.push(key_id.to_owned());
                    ids.push(value_id.to_owned());
                }
            }
            Expression::Lambda(Lambda { body, .. }) => {
                // TODO: list parameter IDs?
                // for (index, _) in parameters.iter().enumerate() {
                //     ids.push(first_id.to_owned() + index);
                // }
                body.collect_all_ids(ids);
            }
            Expression::Body(body) => body.collect_all_ids(ids),
            Expression::Call { arguments, .. } => {
                ids.extend(arguments.iter().cloned());
            }
            Expression::Error => {}
        }
    }
}
impl Body {
    fn collect_all_ids(&self, ids: &mut Vec<Id>) {
        ids.extend(self.expressions.keys().into_iter().cloned());
        for expression in self.expressions.values() {
            expression.collect_all_ids(ids);
        }
    }
}

#[derive(Debug, PartialEq, Eq, Clone, Hash, PartialOrd, Ord)]
pub struct Id {
    pub input: Input,
    pub local: Vec<usize>,
}
impl Id {
    pub fn new(input: Input, local: Vec<usize>) -> Self {
        Self { input, local }
    }

    pub fn is_root(&self) -> bool {
        self.local.is_empty()
    }

    pub fn parent(&self) -> Option<Id> {
        match self.local.len() {
            0 => None,
            _ => Some(Id {
                input: self.input.clone(),
                local: self.local[..self.local.len() - 1].to_vec(),
            }),
        }
    }
}
impl Add<usize> for Id {
    type Output = Self;

    fn add(self, rhs: usize) -> Self::Output {
        let mut local = self.local[..self.local.len() - 1].to_vec();
        local.push(self.local.last().unwrap() + rhs);
        Id {
            input: self.input,
            local: local,
        }
    }
}
impl Display for Id {
    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
        write!(f, "HirId({:?}:{:?})", self.input, self.local)
    }
}

#[derive(Clone, PartialEq, Eq, Hash, Debug)]
pub enum Expression {
    Int(u64),
    Text(String),
    Reference(Id),
    Symbol(String),
    Struct(HashMap<Id, Id>),
    Lambda(Lambda),
    Body(Body),
    Call { function: Id, arguments: Vec<Id> },
    Error,
}
impl Expression {
    pub fn nothing() -> Self {
        Expression::Symbol("Nothing".to_owned())
    }
}

#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Lambda {
    pub first_id: Id,
    pub parameters: Vec<String>,
    pub body: Body,
}

#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Body {
    pub expressions: LinkedHashMap<Id, Expression>,
    pub identifiers: HashMap<Id, String>,
}

impl Body {
    pub fn new() -> Self {
        Self {
            expressions: LinkedHashMap::new(),
            identifiers: HashMap::new(),
        }
    }
    pub fn push(&mut self, id: Id, expression: Expression, identifier: Option<String>) {
        self.expressions.insert(id.to_owned(), expression);
        if let Some(identifier) = identifier {
            self.identifiers.insert(id, identifier);
        }
    }
    pub fn out_id(&self) -> &Id {
        self.expressions.keys().last().unwrap()
    }
}

impl fmt::Display for Expression {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Expression::Int(int) => write!(f, "int {}", int),
            Expression::Text(text) => write!(f, "text {:?}", text),
            Expression::Reference(reference) => write!(f, "reference {}", reference),
            Expression::Symbol(symbol) => write!(f, "symbol {}", symbol),
            Expression::Struct(entries) => {
                write!(
                    f,
                    "struct [\n{}\n]",
                    entries
                        .iter()
                        .map(|(id, value)| format!("  {}: {}", id, value))
                        .join(",\n"),
                )
            }
            Expression::Lambda(lambda) => {
                write!(
                    f,
                    "lambda [\n{}\n]",
                    lambda
                        .to_string()
                        .lines()
                        .map(|line| format!("  {}", line))
                        .join("\n"),
                )
            }
            Expression::Body(body) => {
                write!(
                    f,
                    "body [\n{}\n]",
                    body.to_string()
                        .lines()
                        .map(|line| format!("  {}", line))
                        .join("\n"),
                )
            }
            Expression::Call {
                function,
                arguments,
            } => {
                assert!(arguments.len() > 0, "A call needs to have arguments.");
                write!(f, "call {} with {}", function, arguments.iter().join(" "))
            }
            Expression::Error => write!(f, "<error>"),
        }
    }
}
impl fmt::Display for Lambda {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{} parameters\n", self.parameters.len())?;
        write!(f, "{}", self.body)?;
        Ok(())
    }
}
impl fmt::Display for Body {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        for (id, expression) in self.expressions.iter() {
            write!(f, "{} = {}\n", id, expression)?;
        }
        Ok(())
    }
}

impl Expression {
    fn find(&self, id: &Id) -> Option<&Self> {
        match self {
            Expression::Int { .. } => None,
            Expression::Text { .. } => None,
            Expression::Reference { .. } => None,
            Expression::Symbol { .. } => None,
            Expression::Struct(_) => None,
            Expression::Lambda(Lambda { body, .. }) => body.find(id),
            Expression::Body(body) => body.find(id),
            Expression::Call { .. } => None,
            Expression::Error { .. } => None,
        }
    }
}
impl Body {
    fn find(&self, id: &Id) -> Option<&Expression> {
        if let Some(expression) = self.expressions.get(id) {
            Some(expression)
        } else {
            self.expressions
                .iter()
                .filter(|(key, _)| key <= &id)
                .max_by_key(|(key, _)| key.local.to_owned())?
                .1
                .find(id)
        }
    }
}
<|endoftext|>


<commit_before>/* Copyright 2016 Jordan Miner
 *
 * Licensed under the MIT license <LICENSE or
 * http://opensource.org/licenses/MIT>. This file may not be copied,
 * modified, or distributed except according to those terms.
 */

use std::borrow::Cow;
use std::ops::CoerceUnsized;
use std::ptr;
use iup_sys::*;
use libc::c_int;
use super::{
    Control,
    UnwrapHandle,
};
use super::attributes::{
    ActiveAttribute,
    MinMaxSizeAttribute,
    TipAttribute,
    VisibleAttribute,
    str_to_c_vec,
    get_str_attribute,
    get_str_attribute_slice,
    set_str_attribute,
};
use super::callbacks::{
    CallbackRegistry,
    with_callbacks,
    MenuCommonCallbacks,
    EnterLeaveWindowCallbacks,
    GetKillFocusCallbacks,
    ValueChangedCallback,
    Event,
    Token,
};
use super::handle_rc::HandleRc;

#[derive(Clone)]
pub struct Text(HandleRc);

impl Text {
    pub fn new() -> Self {
        unsafe {
            ::iup_open();
            let ih = IupText(ptr::null_mut());
            Text(HandleRc::new(ih))
        }
    }

    pub fn value(&self) -> String {
        get_str_attribute(self.handle(), "VALUE\0")
    }
    pub unsafe fn value_slice(&self) -> Cow<str> {
        get_str_attribute_slice(self.handle(), "VALUE\0")
    }

    pub fn set_value(&self, value: &str) -> &Self {
        set_str_attribute(self.handle(), "VALUE\0", value);
        self
    }

    pub fn append(&self, text: &str) -> &Self {
        set_str_attribute(self.handle(), "APPEND\0", text);
        self
    }

    pub fn append_newline(&self) -> bool {
        unsafe {
            get_str_attribute_slice(self.handle(), "APPENDNEWLINE\0") == "YES"
        }
    }

    pub fn set_append_newline(&self, enabled: bool) -> &Self {
        set_str_attribute(self.handle(), "APPENDNEWLINE\0", if enabled { "YES\0" } else { "NO\0" });
        self
    }

    pub fn multiline(&self) -> bool {
        unsafe {
            get_str_attribute_slice(self.handle(), "MULTILINE\0") == "YES"
        }
    }

    pub fn set_multiline(&self, multiline: bool) -> &Self {
        set_str_attribute(self.handle(), "MULTILINE\0", if multiline { "YES\0" } else { "NO\0" });
        self
    }

    pub fn visible_lines(&self) -> i32 {
        unsafe {
            let val = get_str_attribute_slice(self.handle(), "VISIBLELINES\0");
            val.parse().expect("could not convert VISIBLELINES to an integer")
        }
    }

    pub fn set_visible_lines(&self, lines: i32) -> &Self {
        set_str_attribute(self.handle(), "VISIBLELINES\0", &lines.to_string());
        self
    }
}

impl_control_traits!(Text);

impl ActiveAttribute for Text {}
impl MinMaxSizeAttribute for Text {}
impl TipAttribute for Text {}
impl VisibleAttribute for Text {}

impl MenuCommonCallbacks for Text {}
impl GetKillFocusCallbacks for Text {}
impl EnterLeaveWindowCallbacks for Text {}
impl ValueChangedCallback for Text {}

impl_callbacks! {
    Text {
        "CARET_CB\0" => caret_event {
            CARET_CALLBACKS<FnMut(usize, usize, usize), CaretCallbackToken>
        }
        unsafe extern fn caret_cb(ih: *mut Ihandle, lin: c_int, col: c_int, pos: c_int) -> c_int {
            with_callbacks(ih, &CARET_CALLBACKS, |cbs| {
                for cb in cbs {
                    (&mut *cb.1.borrow_mut())(lin as usize, col as usize, pos as usize);
                }
                IUP_DEFAULT
            })
        }
    }
}
<commit_msg>Add docs for append and append_newline<commit_after>/* Copyright 2016 Jordan Miner
 *
 * Licensed under the MIT license <LICENSE or
 * http://opensource.org/licenses/MIT>. This file may not be copied,
 * modified, or distributed except according to those terms.
 */

use std::borrow::Cow;
use std::ops::CoerceUnsized;
use std::ptr;
use iup_sys::*;
use libc::c_int;
use super::{
    Control,
    UnwrapHandle,
};
use super::attributes::{
    ActiveAttribute,
    MinMaxSizeAttribute,
    TipAttribute,
    VisibleAttribute,
    str_to_c_vec,
    get_str_attribute,
    get_str_attribute_slice,
    set_str_attribute,
};
use super::callbacks::{
    CallbackRegistry,
    with_callbacks,
    MenuCommonCallbacks,
    EnterLeaveWindowCallbacks,
    GetKillFocusCallbacks,
    ValueChangedCallback,
    Event,
    Token,
};
use super::handle_rc::HandleRc;

#[derive(Clone)]
pub struct Text(HandleRc);

impl Text {
    pub fn new() -> Self {
        unsafe {
            ::iup_open();
            let ih = IupText(ptr::null_mut());
            Text(HandleRc::new(ih))
        }
    }

    pub fn value(&self) -> String {
        get_str_attribute(self.handle(), "VALUE\0")
    }
    pub unsafe fn value_slice(&self) -> Cow<str> {
        get_str_attribute_slice(self.handle(), "VALUE\0")
    }

    pub fn set_value(&self, value: &str) -> &Self {
        set_str_attribute(self.handle(), "VALUE\0", value);
        self
    }

    /// Inserts text at the end of the current text. Ignored if called before the control is mapped.
    /// If this control is multiline, [`append_newline`](./struct.Text.html#method.append_newline)
    /// is true, and the current text is not empty, then a `'\n'` character will be automatically
    /// inserted before the appended text.
    pub fn append(&self, text: &str) -> &Self {
        set_str_attribute(self.handle(), "APPEND\0", text);
        self
    }

    /// If true, this control is multiline, and the current text is not empty, then a `'\n'`
    /// character will be automatically inserted before text appended using `append`.
    pub fn append_newline(&self) -> bool {
        unsafe {
            get_str_attribute_slice(self.handle(), "APPENDNEWLINE\0") == "YES"
        }
    }

    /// See [`append_newline`](./struct.Text.html#method.append_newline)
    pub fn set_append_newline(&self, enabled: bool) -> &Self {
        set_str_attribute(self.handle(), "APPENDNEWLINE\0", if enabled { "YES\0" } else { "NO\0" });
        self
    }

    pub fn multiline(&self) -> bool {
        unsafe {
            get_str_attribute_slice(self.handle(), "MULTILINE\0") == "YES"
        }
    }

    pub fn set_multiline(&self, multiline: bool) -> &Self {
        set_str_attribute(self.handle(), "MULTILINE\0", if multiline { "YES\0" } else { "NO\0" });
        self
    }

    pub fn visible_lines(&self) -> i32 {
        unsafe {
            let val = get_str_attribute_slice(self.handle(), "VISIBLELINES\0");
            val.parse().expect("could not convert VISIBLELINES to an integer")
        }
    }

    pub fn set_visible_lines(&self, lines: i32) -> &Self {
        set_str_attribute(self.handle(), "VISIBLELINES\0", &lines.to_string());
        self
    }
}

impl_control_traits!(Text);

impl ActiveAttribute for Text {}
impl MinMaxSizeAttribute for Text {}
impl TipAttribute for Text {}
impl VisibleAttribute for Text {}

impl MenuCommonCallbacks for Text {}
impl GetKillFocusCallbacks for Text {}
impl EnterLeaveWindowCallbacks for Text {}
impl ValueChangedCallback for Text {}

impl_callbacks! {
    Text {
        "CARET_CB\0" => caret_event {
            CARET_CALLBACKS<FnMut(usize, usize, usize), CaretCallbackToken>
        }
        unsafe extern fn caret_cb(ih: *mut Ihandle, lin: c_int, col: c_int, pos: c_int) -> c_int {
            with_callbacks(ih, &CARET_CALLBACKS, |cbs| {
                for cb in cbs {
                    (&mut *cb.1.borrow_mut())(lin as usize, col as usize, pos as usize);
                }
                IUP_DEFAULT
            })
        }
    }
}
<|endoftext|>


<commit_before>// Copyright 2014 The Rustdown Developers. See the COPYRIGHT
// file at the top-level directory of this distribution.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

use std::str::CharEq;
use std::fmt::Show;

/// If string `s` starts with `c`, then return the remaining characters
/// after `c` has been trimmed from the beginning, along with the number
/// of occurrences of `c` in the beginning of string `s`.
pub fn starting_chars(s: &str, c: char) -> Option<(String, uint)> {
    let mut result = None;
    if s.trim_chars(c) != s {
        let mut count = 0u;
        let mut found = false;
        let words: String = s.chars().filter_map(
            |letter| {
                match letter {
                    l if c == letter && !found => {
                        count += 1;
                        None
                    }
                    other => {
                        found = true;
                        Some(other)
                    }
                }
            }
        ).collect();
        result = Some((words.as_slice().trim_left_chars(' ').to_string(), count));
    }
    return result;
}

pub fn all_chars_are(c: char, s: &str) -> bool {
    let cs = c.to_string();
    let ch = cs.as_slice();
    if s.starts_with(ch) {
        for badchar in s.as_slice().chars().filter_map(
                    |e| if e == c { None } else { Some(c) }) {
            return false;
        }
        return true;
    }
    false
}

#[cfg(test)]
mod tests {
    use super::starting_chars;
    use super::all_chars_are;

    #[test]
    fn test_all_chars_are_fail() {
        assert!(!all_chars_are('-', "- This is a bullet"));
        assert!(!all_chars_are('-', ""));
        assert!(!all_chars_are('a', "aaaba"));
        assert!(!all_chars_are('a', "aaab"));
        assert!(!all_chars_are('a', "baaa"));
    }

    #[test]
    fn test_all_chars_are_pass() {
        assert!(all_chars_are('-', "------------"));
        assert!(all_chars_are('=', "="));
    }

    #[test]
    fn test_starting_chars() {
        assert_eq!(starting_chars("### haha", '#'), Some(("haha".to_string(), 3)));
        assert_eq!(starting_chars("    - bullet", ' '), Some(("- bullet".to_string(), 4)));
    }
}
<commit_msg>text: no more need for a match statement<commit_after>// Copyright 2014 The Rustdown Developers. See the COPYRIGHT
// file at the top-level directory of this distribution.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

use std::str::CharEq;
use std::fmt::Show;

/// If string `s` starts with `c`, then return the remaining characters
/// after `c` has been trimmed from the beginning, along with the number
/// of occurrences of `c` in the beginning of string `s`.
pub fn starting_chars(s: &str, c: char) -> Option<(String, uint)> {
    let mut result = None;
    if s.trim_chars(c) != s {
        let mut count = 0u;
        let mut found = false;
        let words: String = s.chars().filter_map(
            |letter| {
                if c == letter && !found {
                    count += 1;
                    None
                }
                else {
                    found = true;
                    Some(letter)
                }
            }
        ).collect();
        result = Some((words.as_slice().trim_left_chars(' ').to_string(), count));
    }
    return result;
}

pub fn all_chars_are(c: char, s: &str) -> bool {
    let cs = c.to_string();
    let ch = cs.as_slice();
    if s.starts_with(ch) {
        for badchar in s.as_slice().chars().filter_map(
                    |e| if e == c { None } else { Some(c) }) {
            return false;
        }
        return true;
    }
    false
}

#[cfg(test)]
mod tests {
    use super::starting_chars;
    use super::all_chars_are;

    #[test]
    fn test_all_chars_are_fail() {
        assert!(!all_chars_are('-', "- This is a bullet"));
        assert!(!all_chars_are('-', ""));
        assert!(!all_chars_are('a', "aaaba"));
        assert!(!all_chars_are('a', "aaab"));
        assert!(!all_chars_are('a', "baaa"));
    }

    #[test]
    fn test_all_chars_are_pass() {
        assert!(all_chars_are('-', "------------"));
        assert!(all_chars_are('=', "="));
    }

    #[test]
    fn test_starting_chars() {
        assert_eq!(starting_chars("### haha", '#'), Some(("haha".to_string(), 3)));
        assert_eq!(starting_chars("    - bullet", ' '), Some(("- bullet".to_string(), 4)));
    }
}
<|endoftext|>


<commit_before>use std::vec::Vec;
use super::Error;
use super::types::SqlType;
use super::types::Column;
use std::io::{Write, Read, Seek};

#[derive(Debug, RustcEncodable, RustcDecodable)]
pub struct Rows <B: Write + Read + Seek> {
    buf: B,
    columns: Vec<Column>,
    pos: u64,
}

/// Represents the lines read from file.
impl<B: Write + Read + Seek> Rows <B> {

    /// sets pos to the beginning of the next non-deleted line
    /// returns false if no next line could be found
    pub fn next_row(&mut self) -> bool {
        false
    }

    /// checks if a next line exists
    fn has_next(&self) -> bool {
        false
    }

    /// reads the current row and writes the data into target_buf
    /// returns the bytes read
    pub fn read_row<W: Write>(&self, target_buf: &W) -> Result<u64, Error> {
        Err(Error::NoImplementation)
    }

    /// sets position before the first line
    pub fn reset_pos(&mut self) {
        self.set_pos(OUT_OF_RANGE);
    }

    /// sets position to offset
    fn set_pos(&mut self, offset: u64) {
        self.pos = offset;
    }

    /// reads the header of the current row
    /// returns an error if no RowHeader exists
    pub fn read_header(&self) -> Result <RowHeader, Error> {
        Err(Error::NoImplementation)
    }

    /// writes a new row into buf, returns bytes written
    pub fn add_row(&self, row_data: &[u8]) -> Result<u64, Error> {
        Err(Error::NoImplementation)
    }

    /// returns the value of the column_index' column of the current row
    /// returns an error if no current row exists
    pub fn get_value(&self, column_index: usize) -> Result<Vec<u8>, Error> {
        Err(Error::NoImplementation)
    }

    /// returns true if a row can be read
    fn on_row(&self) -> bool {
        false
    }

}

pub struct RowHeader{
    data: u8,
}

impl RowHeader{
    /// returns true if the current row is marked as deleted
    pub fn is_deleted(&self) -> bool {
        false
    }

    /// marks row as deleted
    pub fn set_deleted(&mut self) {

    }
}
<commit_msg>Thinking about next_row()<commit_after>use std::vec::Vec;
use super::Error;
use super::types::SqlType;
use super::types::Column;
use std::io::{Write, Read, Seek};

#[derive(Debug, RustcEncodable, RustcDecodable)]
pub struct Rows <B: Write + Read + Seek> {
    buf: B,
    columns: Vec<Column>,
}

/// Represents the lines read from file.
impl<B: Write + Read + Seek> Rows <B> {

    /// reads the next row, which is not marked as deleted
    /// and writes it into target_buf
    /// returns true, if the next row could be read successfully
    pub fn next_row<W: Write>(&mut self, mut target_buf: &W) -> bool {
        self.buf.read();
    }

    /// checks if a next line exists
    fn has_next(&self) -> bool {
        false
    }

    /// reads the current row and writes the data into target_buf
    /// returns the bytes read
    pub fn read_row<W: Write>(&self, target_buf: &W) -> Result<u64, Error> {
        Err(Error::NoImplementation)
    }

    /// sets position before the first line
    pub fn reset_pos(&mut self) {
    }

    /// sets position to offset
    fn set_pos(&mut self, offset: u64) {

    }

    /// reads the header of the current row
    /// returns an error if no RowHeader exists
    pub fn read_header(&self) -> Result <RowHeader, Error> {
        Err(Error::NoImplementation)
    }

    /// writes a new row into buf, returns bytes written
    pub fn add_row(&self, row_data: &[u8]) -> Result<u64, Error> {
        Err(Error::NoImplementation)
    }

    /// returns the value of the column_index' column of the current row
    /// returns an error if no current row exists
    pub fn get_value(&self, column_index: usize) -> Result<Vec<u8>, Error> {
        Err(Error::NoImplementation)
    }

    /// returns true if a row can be read
    fn on_row(&self) -> bool {
        false
    }

}

pub struct RowHeader{
    data: u8,
}

impl RowHeader{
    /// returns true if the current row is marked as deleted
    pub fn is_deleted(&self) -> bool {
        false
    }

    /// marks row as deleted
    pub fn set_deleted(&mut self) {

    }
}
<|endoftext|>


<commit_before>use crate::App;
use anyhow::Error;
use dashmap::{DashMap, SharedValue};
use diesel::{pg::upsert::excluded, prelude::*};
use std::collections::HashMap;
use std::sync::atomic::{AtomicI64, AtomicUsize, Ordering};

/// crates.io receives a lot of download requests, and we can't execute a write query to the
/// database during each connection for performance reasons. To reduce the write load, this struct
/// collects the pending updates from the current process and writes in batch.
///
/// To avoid locking the whole data structure behind a RwLock, which could potentially delay
/// requests, this uses the dashmap crate. A DashMap has the same public API as an HashMap, but
/// stores the items into `num_cpus()*4` individually locked shards. This approach reduces the
/// likelyhood of a request encountering a locked shard.
///
/// Persisting the download counts in the database also takes advantage of the inner sharding of
/// DashMaps: to avoid locking all the download requests at the same time each iteration only
/// persists a single shard at the time.
///
/// The disadvantage of this approach is that download counts are stored in memory until they're
/// persisted, so it's possible to lose some of them if the process exits ungracefully. While
/// that's far from ideal, the advantage of batching database updates far outweights potentially
/// losing some download counts.
#[derive(Debug)]
pub struct DownloadsCounter {
    /// Inner storage for the download counts.
    inner: DashMap<i32, AtomicUsize>,
    /// Index of the next shard that should be persisted by `persist_next_shard`.
    shard_idx: AtomicUsize,
    /// Number of downloads that are not yet persisted on the database. This is just used as a
    /// metric included in log lines, and it's not guaranteed to be accurate.
    pending_count: AtomicI64,
}

impl DownloadsCounter {
    pub(crate) fn new() -> Self {
        Self {
            inner: DashMap::new(),
            shard_idx: AtomicUsize::new(0),
            pending_count: AtomicI64::new(0),
        }
    }

    pub(crate) fn increment(&self, version_id: i32) {
        self.pending_count.fetch_add(1, Ordering::SeqCst);

        if let Some(counter) = self.inner.get(&version_id) {
            // The version is already recorded in the DashMap, so we don't need to lock the whole
            // shard in write mode. The shard is instead locked in read mode, which allows an
            // unbounded number of readers as long as there are no write locks.
            counter.value().fetch_add(1, Ordering::SeqCst);
        } else {
            // The version is not in the DashMap, so we need to lock the whole shard in write mode
            // and insert the version into it. This has worse performance than the above case.
            self.inner
                .entry(version_id)
                .and_modify(|counter| {
                    // Handle the version being inserted by another thread while we were waiting
                    // for the write lock on the shard.
                    counter.fetch_add(1, Ordering::SeqCst);
                })
                .or_insert_with(|| AtomicUsize::new(1));
        }
    }

    pub fn persist_all_shards(&self, app: &App) -> Result<(), Error> {
        let conn = app.primary_database.get()?;

        let mut counted_downloads = 0;
        let mut counted_versions = 0;
        let mut pending_downloads = 0;
        for shard in self.inner.shards() {
            let shard = std::mem::take(&mut *shard.write());
            let stats = self.persist_shard(&conn, shard)?;

            counted_downloads += stats.counted_downloads;
            counted_versions += stats.counted_versions;
            pending_downloads = stats.pending_downloads;
        }

        println!(
            "download_counter all_shards counted_versions={} counted_downloads={} pending_downloads={}",
            counted_versions,
            counted_downloads,
            pending_downloads,
        );

        Ok(())
    }

    pub fn persist_next_shard(&self, app: &App) -> Result<(), Error> {
        let conn = app.primary_database.get()?;

        // Replace the next shard in the ring with an empty HashMap (clearing it), and return the
        // previous contents for processing. The fetch_add method wraps around on overflow, so it's
        // fine to keep incrementing it without resetting.
        let shards = self.inner.shards();
        let idx = self.shard_idx.fetch_add(1, Ordering::SeqCst) % shards.len();
        let shard = std::mem::take(&mut *shards[idx].write());

        let stats = self.persist_shard(&conn, shard)?;
        println!(
            "download_counter shard={} counted_versions={} counted_downloads={} pending_downloads={}",
            idx,
            stats.counted_versions,
            stats.counted_downloads,
            stats.pending_downloads,
        );

        Ok(())
    }

    fn persist_shard(
        &self,
        conn: &PgConnection,
        shard: HashMap<i32, SharedValue<AtomicUsize>>,
    ) -> Result<PersistStats, Error> {
        use crate::schema::version_downloads::dsl::*;

        let mut counted_downloads = 0;
        let mut counted_versions = 0;
        let mut to_insert = Vec::new();
        for (key, atomic) in shard.iter() {
            let count = atomic.get().load(Ordering::SeqCst);
            counted_downloads += count;
            counted_versions += 1;

            to_insert.push((*key, count));
        }

        if !to_insert.is_empty() {
            // The rows we're about to insert need to be sorted to avoid deadlocks when multiple
            // instances of crates.io are running at the same time.
            //
            // In PostgreSQL a transaction modifying a row locks that row until the transaction is
            // committed. Multiple transactions inserting rows into a table could end up
            // deadlocking each other though: PostgreSQL will detect that deadlock, abort one of
            // the transactions and allow the other one to continue. We don't want that to happen,
            // as we'd lose the downloads from the aborted transaction.
            //
            // Ensuring the rows are inserted in a consistent order (in our case by sorting them by
            // the version ID) will prevent deadlocks from occuring. For more information:
            //
            //     https://www.postgresql.org/docs/11/explicit-locking.html#LOCKING-DEADLOCKS
            //
            to_insert.sort_by_key(|(key, _)| *key);

            let to_insert = to_insert
                .into_iter()
                .map(|(key, count)| (version_id.eq(key), downloads.eq(count as i32)))
                .collect::<Vec<_>>();

            diesel::insert_into(version_downloads)
                .values(&to_insert)
                .on_conflict((version_id, date))
                .do_update()
                .set(downloads.eq(downloads + excluded(downloads)))
                .execute(conn)?;
        }

        let old_pending = self
            .pending_count
            .fetch_sub(counted_downloads as i64, Ordering::SeqCst);

        Ok(PersistStats {
            counted_downloads,
            counted_versions,
            pending_downloads: old_pending - counted_downloads as i64,
        })
    }

    pub fn shards_count(&self) -> usize {
        self.inner.shards().len()
    }
}

struct PersistStats {
    counted_downloads: usize,
    counted_versions: usize,
    pending_downloads: i64,
}
<commit_msg>Use consistent pluralization for log entries<commit_after>use crate::App;
use anyhow::Error;
use dashmap::{DashMap, SharedValue};
use diesel::{pg::upsert::excluded, prelude::*};
use std::collections::HashMap;
use std::sync::atomic::{AtomicI64, AtomicUsize, Ordering};

/// crates.io receives a lot of download requests, and we can't execute a write query to the
/// database during each connection for performance reasons. To reduce the write load, this struct
/// collects the pending updates from the current process and writes in batch.
///
/// To avoid locking the whole data structure behind a RwLock, which could potentially delay
/// requests, this uses the dashmap crate. A DashMap has the same public API as an HashMap, but
/// stores the items into `num_cpus()*4` individually locked shards. This approach reduces the
/// likelyhood of a request encountering a locked shard.
///
/// Persisting the download counts in the database also takes advantage of the inner sharding of
/// DashMaps: to avoid locking all the download requests at the same time each iteration only
/// persists a single shard at the time.
///
/// The disadvantage of this approach is that download counts are stored in memory until they're
/// persisted, so it's possible to lose some of them if the process exits ungracefully. While
/// that's far from ideal, the advantage of batching database updates far outweights potentially
/// losing some download counts.
#[derive(Debug)]
pub struct DownloadsCounter {
    /// Inner storage for the download counts.
    inner: DashMap<i32, AtomicUsize>,
    /// Index of the next shard that should be persisted by `persist_next_shard`.
    shard_idx: AtomicUsize,
    /// Number of downloads that are not yet persisted on the database. This is just used as a
    /// metric included in log lines, and it's not guaranteed to be accurate.
    pending_count: AtomicI64,
}

impl DownloadsCounter {
    pub(crate) fn new() -> Self {
        Self {
            inner: DashMap::new(),
            shard_idx: AtomicUsize::new(0),
            pending_count: AtomicI64::new(0),
        }
    }

    pub(crate) fn increment(&self, version_id: i32) {
        self.pending_count.fetch_add(1, Ordering::SeqCst);

        if let Some(counter) = self.inner.get(&version_id) {
            // The version is already recorded in the DashMap, so we don't need to lock the whole
            // shard in write mode. The shard is instead locked in read mode, which allows an
            // unbounded number of readers as long as there are no write locks.
            counter.value().fetch_add(1, Ordering::SeqCst);
        } else {
            // The version is not in the DashMap, so we need to lock the whole shard in write mode
            // and insert the version into it. This has worse performance than the above case.
            self.inner
                .entry(version_id)
                .and_modify(|counter| {
                    // Handle the version being inserted by another thread while we were waiting
                    // for the write lock on the shard.
                    counter.fetch_add(1, Ordering::SeqCst);
                })
                .or_insert_with(|| AtomicUsize::new(1));
        }
    }

    pub fn persist_all_shards(&self, app: &App) -> Result<(), Error> {
        let conn = app.primary_database.get()?;

        let mut counted_downloads = 0;
        let mut counted_versions = 0;
        let mut pending_downloads = 0;
        for shard in self.inner.shards() {
            let shard = std::mem::take(&mut *shard.write());
            let stats = self.persist_shard(&conn, shard)?;

            counted_downloads += stats.counted_downloads;
            counted_versions += stats.counted_versions;
            pending_downloads = stats.pending_downloads;
        }

        println!(
            "downloads_counter all_shards counted_versions={} counted_downloads={} pending_downloads={}",
            counted_versions,
            counted_downloads,
            pending_downloads,
        );

        Ok(())
    }

    pub fn persist_next_shard(&self, app: &App) -> Result<(), Error> {
        let conn = app.primary_database.get()?;

        // Replace the next shard in the ring with an empty HashMap (clearing it), and return the
        // previous contents for processing. The fetch_add method wraps around on overflow, so it's
        // fine to keep incrementing it without resetting.
        let shards = self.inner.shards();
        let idx = self.shard_idx.fetch_add(1, Ordering::SeqCst) % shards.len();
        let shard = std::mem::take(&mut *shards[idx].write());

        let stats = self.persist_shard(&conn, shard)?;
        println!(
            "downloads_counter shard={} counted_versions={} counted_downloads={} pending_downloads={}",
            idx,
            stats.counted_versions,
            stats.counted_downloads,
            stats.pending_downloads,
        );

        Ok(())
    }

    fn persist_shard(
        &self,
        conn: &PgConnection,
        shard: HashMap<i32, SharedValue<AtomicUsize>>,
    ) -> Result<PersistStats, Error> {
        use crate::schema::version_downloads::dsl::*;

        let mut counted_downloads = 0;
        let mut counted_versions = 0;
        let mut to_insert = Vec::new();
        for (key, atomic) in shard.iter() {
            let count = atomic.get().load(Ordering::SeqCst);
            counted_downloads += count;
            counted_versions += 1;

            to_insert.push((*key, count));
        }

        if !to_insert.is_empty() {
            // The rows we're about to insert need to be sorted to avoid deadlocks when multiple
            // instances of crates.io are running at the same time.
            //
            // In PostgreSQL a transaction modifying a row locks that row until the transaction is
            // committed. Multiple transactions inserting rows into a table could end up
            // deadlocking each other though: PostgreSQL will detect that deadlock, abort one of
            // the transactions and allow the other one to continue. We don't want that to happen,
            // as we'd lose the downloads from the aborted transaction.
            //
            // Ensuring the rows are inserted in a consistent order (in our case by sorting them by
            // the version ID) will prevent deadlocks from occuring. For more information:
            //
            //     https://www.postgresql.org/docs/11/explicit-locking.html#LOCKING-DEADLOCKS
            //
            to_insert.sort_by_key(|(key, _)| *key);

            let to_insert = to_insert
                .into_iter()
                .map(|(key, count)| (version_id.eq(key), downloads.eq(count as i32)))
                .collect::<Vec<_>>();

            diesel::insert_into(version_downloads)
                .values(&to_insert)
                .on_conflict((version_id, date))
                .do_update()
                .set(downloads.eq(downloads + excluded(downloads)))
                .execute(conn)?;
        }

        let old_pending = self
            .pending_count
            .fetch_sub(counted_downloads as i64, Ordering::SeqCst);

        Ok(PersistStats {
            counted_downloads,
            counted_versions,
            pending_downloads: old_pending - counted_downloads as i64,
        })
    }

    pub fn shards_count(&self) -> usize {
        self.inner.shards().len()
    }
}

struct PersistStats {
    counted_downloads: usize,
    counted_versions: usize,
    pending_downloads: i64,
}
<|endoftext|>


<commit_before>use super::*;
use futures::channel::oneshot;
use futures::executor::block_on;
use std::io::Write;
use std::mem;
use std::path::{Path, PathBuf};
use std::sync::atomic::Ordering::SeqCst;
use std::sync::atomic::{AtomicBool, AtomicUsize};
use std::sync::Arc;
use std::time::Duration;

#[cfg(feature = "mmap")]
mod mmap_directory_tests {
    use crate::directory::MmapDirectory;

    type DirectoryImpl = MmapDirectory;

    fn make_directory() -> DirectoryImpl {
        MmapDirectory::create_from_tempdir().unwrap()
    }

    #[test]
    fn test_simple() {
        let mut directory = make_directory();
        super::test_simple(&mut directory);
    }

    #[test]
    fn test_write_create_the_file() {
        let mut directory = make_directory();
        super::test_write_create_the_file(&mut directory);
    }

    #[test]
    fn test_rewrite_forbidden() {
        let mut directory = make_directory();
        super::test_rewrite_forbidden(&mut directory);
    }

    #[test]
    fn test_directory_delete() {
        let mut directory = make_directory();
        super::test_directory_delete(&mut directory);
    }

    #[test]
    fn test_lock_non_blocking() {
        let mut directory = make_directory();
        super::test_lock_non_blocking(&mut directory);
    }

    #[test]
    fn test_lock_blocking() {
        let mut directory = make_directory();
        super::test_lock_blocking(&mut directory);
    }

    #[test]
    fn test_watch() {
        let mut directory = make_directory();
        super::test_watch(&mut directory);
    }
}

mod ram_directory_tests {
    use crate::directory::RAMDirectory;

    type DirectoryImpl = RAMDirectory;

    fn make_directory() -> DirectoryImpl {
        RAMDirectory::default()
    }

    #[test]
    fn test_simple() {
        let mut directory = make_directory();
        super::test_simple(&mut directory);
    }

    #[test]
    fn test_write_create_the_file() {
        let mut directory = make_directory();
        super::test_write_create_the_file(&mut directory);
    }

    #[test]
    fn test_rewrite_forbidden() {
        let mut directory = make_directory();
        super::test_rewrite_forbidden(&mut directory);
    }

    #[test]
    fn test_directory_delete() {
        let mut directory = make_directory();
        super::test_directory_delete(&mut directory);
    }

    #[test]
    fn test_lock_non_blocking() {
        let mut directory = make_directory();
        super::test_lock_non_blocking(&mut directory);
    }

    #[test]
    fn test_lock_blocking() {
        let mut directory = make_directory();
        super::test_lock_blocking(&mut directory);
    }

    #[test]
    fn test_watch() {
        let mut directory = make_directory();
        super::test_watch(&mut directory);
    }
}

#[test]
#[should_panic]
fn ram_directory_panics_if_flush_forgotten() {
    let test_path: &'static Path = Path::new("some_path_for_test");
    let mut ram_directory = RAMDirectory::create();
    let mut write_file = ram_directory.open_write(test_path).unwrap();
    assert!(write_file.write_all(&[4]).is_ok());
}

fn test_simple(directory: &mut dyn Directory) {
    let test_path: &'static Path = Path::new("some_path_for_test");
    {
        let mut write_file = directory.open_write(test_path).unwrap();
        assert!(directory.exists(test_path));
        write_file.write_all(&[4]).unwrap();
        write_file.write_all(&[3]).unwrap();
        write_file.write_all(&[7, 3, 5]).unwrap();
        write_file.flush().unwrap();
    }
    {
        let read_file = directory.open_read(test_path).unwrap();
        let data: &[u8] = &*read_file;
        assert_eq!(data, &[4u8, 3u8, 7u8, 3u8, 5u8]);
    }
    assert!(directory.delete(test_path).is_ok());
    assert!(!directory.exists(test_path));
}

fn test_rewrite_forbidden(directory: &mut dyn Directory) {
    let test_path: &'static Path = Path::new("some_path_for_test");
    {
        directory.open_write(test_path).unwrap();
        assert!(directory.exists(test_path));
    }
    {
        assert!(directory.open_write(test_path).is_err());
    }
    assert!(directory.delete(test_path).is_ok());
}

fn test_write_create_the_file(directory: &mut dyn Directory) {
    let test_path: &'static Path = Path::new("some_path_for_test");
    {
        assert!(directory.open_read(test_path).is_err());
        let _w = directory.open_write(test_path).unwrap();
        assert!(directory.exists(test_path));
        assert!(directory.open_read(test_path).is_ok());
        assert!(directory.delete(test_path).is_ok());
    }
}

fn test_directory_delete(directory: &mut dyn Directory) {
    let test_path: &'static Path = Path::new("some_path_for_test");
    assert!(directory.open_read(test_path).is_err());
    let mut write_file = directory.open_write(&test_path).unwrap();
    write_file.write_all(&[1, 2, 3, 4]).unwrap();
    write_file.flush().unwrap();
    {
        let read_handle = directory.open_read(&test_path).unwrap();
        assert_eq!(&*read_handle, &[1u8, 2u8, 3u8, 4u8]);
        // Mapped files can't be deleted on Windows
        if !cfg!(windows) {
            assert!(directory.delete(&test_path).is_ok());
            assert_eq!(&*read_handle, &[1u8, 2u8, 3u8, 4u8]);
        }

        assert!(directory.delete(Path::new("SomeOtherPath")).is_err());
    }

    if cfg!(windows) {
        assert!(directory.delete(&test_path).is_ok());
    }

    assert!(directory.open_read(&test_path).is_err());
    assert!(directory.delete(&test_path).is_err());
}

fn test_watch(directory: &mut dyn Directory) {
    let num_progress: Arc<AtomicUsize> = Default::default();
    let counter: Arc<AtomicUsize> = Default::default();
    let counter_clone = counter.clone();
    let (sender, receiver) = crossbeam::channel::unbounded();
    let watch_callback = Box::new(move || {
        counter_clone.fetch_add(1, SeqCst);
    });
    // This callback is used to synchronize watching in our unit test.
    // We bind it to a variable because the callback is removed when that
    // handle is dropped.
    let watch_handle = directory.watch(watch_callback).unwrap();
    let _progress_listener = directory
        .watch(Box::new(move || {
            let val = num_progress.fetch_add(1, SeqCst);
            let _ = sender.send(val);
        }))
        .unwrap();

    for i in 0..10 {
        assert!(i <= counter.load(SeqCst));
        assert!(directory
            .atomic_write(Path::new("meta.json"), b"random_test_data_2")
            .is_ok());
        assert!(i + 1 <= counter.load(SeqCst)); // notify can trigger more than once.
    }
    mem::drop(watch_handle);
    assert!(directory
        .atomic_write(Path::new("meta.json"), b"random_test_data")
        .is_ok());
    assert!(receiver.recv_timeout(Duration::from_millis(500)).is_ok());
    assert!(10 <= counter.load(SeqCst));
}

fn test_lock_non_blocking(directory: &mut dyn Directory) {
    {
        let lock_a_res = directory.acquire_lock(&Lock {
            filepath: PathBuf::from("a.lock"),
            is_blocking: false,
        });
        assert!(lock_a_res.is_ok());
        let lock_b_res = directory.acquire_lock(&Lock {
            filepath: PathBuf::from("b.lock"),
            is_blocking: false,
        });
        assert!(lock_b_res.is_ok());
        let lock_a_res2 = directory.acquire_lock(&Lock {
            filepath: PathBuf::from("a.lock"),
            is_blocking: false,
        });
        assert!(lock_a_res2.is_err());
    }
    let lock_a_res = directory.acquire_lock(&Lock {
        filepath: PathBuf::from("a.lock"),
        is_blocking: false,
    });
    assert!(lock_a_res.is_ok());
}

fn test_lock_blocking(directory: &mut dyn Directory) {
    let lock_a_res = directory.acquire_lock(&Lock {
        filepath: PathBuf::from("a.lock"),
        is_blocking: true,
    });
    assert!(lock_a_res.is_ok());
    let in_thread = Arc::new(AtomicBool::default());
    let in_thread_clone = in_thread.clone();
    let (sender, receiver) = oneshot::channel();
    std::thread::spawn(move || {
        //< lock_a_res is sent to the thread.
        in_thread_clone.store(true, SeqCst);
        let _just_sync = block_on(receiver);
        // explicitely droping lock_a_res. It would have been sufficient to just force it
        // to be part of the move, but the intent seems clearer that way.
        drop(lock_a_res);
    });
    {
        // A non-blocking call should fail, as the thread is running and holding the lock.
        let lock_a_res = directory.acquire_lock(&Lock {
            filepath: PathBuf::from("a.lock"),
            is_blocking: false,
        });
        assert!(lock_a_res.is_err());
    }
    let directory_clone = directory.box_clone();
    let (sender2, receiver2) = oneshot::channel();
    let join_handle = std::thread::spawn(move || {
        assert!(sender2.send(()).is_ok());
        let lock_a_res = directory_clone.acquire_lock(&Lock {
            filepath: PathBuf::from("a.lock"),
            is_blocking: true,
        });
        assert!(in_thread.load(SeqCst));
        assert!(lock_a_res.is_ok());
    });
    assert!(block_on(receiver2).is_ok());
    assert!(sender.send(()).is_ok());
    assert!(join_handle.join().is_ok());
}
<commit_msg>fix unit test (at least on linux)<commit_after>use super::*;
use futures::channel::oneshot;
use futures::executor::block_on;
use std::io::Write;
use std::mem;
use std::path::{Path, PathBuf};
use std::sync::atomic::Ordering::SeqCst;
use std::sync::atomic::{AtomicBool, AtomicUsize};
use std::sync::Arc;
use std::time::Duration;

#[cfg(feature = "mmap")]
mod mmap_directory_tests {
    use crate::directory::MmapDirectory;

    type DirectoryImpl = MmapDirectory;

    fn make_directory() -> DirectoryImpl {
        MmapDirectory::create_from_tempdir().unwrap()
    }

    #[test]
    fn test_simple() {
        let mut directory = make_directory();
        super::test_simple(&mut directory);
    }

    #[test]
    fn test_write_create_the_file() {
        let mut directory = make_directory();
        super::test_write_create_the_file(&mut directory);
    }

    #[test]
    fn test_rewrite_forbidden() {
        let mut directory = make_directory();
        super::test_rewrite_forbidden(&mut directory);
    }

    #[test]
    fn test_directory_delete() {
        let mut directory = make_directory();
        super::test_directory_delete(&mut directory);
    }

    #[test]
    fn test_lock_non_blocking() {
        let mut directory = make_directory();
        super::test_lock_non_blocking(&mut directory);
    }

    #[test]
    fn test_lock_blocking() {
        let mut directory = make_directory();
        super::test_lock_blocking(&mut directory);
    }

    #[test]
    fn test_watch() {
        let mut directory = make_directory();
        super::test_watch(&mut directory);
    }
}

mod ram_directory_tests {
    use crate::directory::RAMDirectory;

    type DirectoryImpl = RAMDirectory;

    fn make_directory() -> DirectoryImpl {
        RAMDirectory::default()
    }

    #[test]
    fn test_simple() {
        let mut directory = make_directory();
        super::test_simple(&mut directory);
    }

    #[test]
    fn test_write_create_the_file() {
        let mut directory = make_directory();
        super::test_write_create_the_file(&mut directory);
    }

    #[test]
    fn test_rewrite_forbidden() {
        let mut directory = make_directory();
        super::test_rewrite_forbidden(&mut directory);
    }

    #[test]
    fn test_directory_delete() {
        let mut directory = make_directory();
        super::test_directory_delete(&mut directory);
    }

    #[test]
    fn test_lock_non_blocking() {
        let mut directory = make_directory();
        super::test_lock_non_blocking(&mut directory);
    }

    #[test]
    fn test_lock_blocking() {
        let mut directory = make_directory();
        super::test_lock_blocking(&mut directory);
    }

    #[test]
    fn test_watch() {
        let mut directory = make_directory();
        super::test_watch(&mut directory);
    }
}

#[test]
#[should_panic]
fn ram_directory_panics_if_flush_forgotten() {
    let test_path: &'static Path = Path::new("some_path_for_test");
    let mut ram_directory = RAMDirectory::create();
    let mut write_file = ram_directory.open_write(test_path).unwrap();
    assert!(write_file.write_all(&[4]).is_ok());
}

fn test_simple(directory: &mut dyn Directory) {
    let test_path: &'static Path = Path::new("some_path_for_test");
    {
        let mut write_file = directory.open_write(test_path).unwrap();
        assert!(directory.exists(test_path));
        write_file.write_all(&[4]).unwrap();
        write_file.write_all(&[3]).unwrap();
        write_file.write_all(&[7, 3, 5]).unwrap();
        write_file.flush().unwrap();
    }
    {
        let read_file = directory.open_read(test_path).unwrap();
        let data: &[u8] = &*read_file;
        assert_eq!(data, &[4u8, 3u8, 7u8, 3u8, 5u8]);
    }
    assert!(directory.delete(test_path).is_ok());
    assert!(!directory.exists(test_path));
}

fn test_rewrite_forbidden(directory: &mut dyn Directory) {
    let test_path: &'static Path = Path::new("some_path_for_test");
    {
        directory.open_write(test_path).unwrap();
        assert!(directory.exists(test_path));
    }
    {
        assert!(directory.open_write(test_path).is_err());
    }
    assert!(directory.delete(test_path).is_ok());
}

fn test_write_create_the_file(directory: &mut dyn Directory) {
    let test_path: &'static Path = Path::new("some_path_for_test");
    {
        assert!(directory.open_read(test_path).is_err());
        let _w = directory.open_write(test_path).unwrap();
        assert!(directory.exists(test_path));
        assert!(directory.open_read(test_path).is_ok());
        assert!(directory.delete(test_path).is_ok());
    }
}

fn test_directory_delete(directory: &mut dyn Directory) {
    let test_path: &'static Path = Path::new("some_path_for_test");
    assert!(directory.open_read(test_path).is_err());
    let mut write_file = directory.open_write(&test_path).unwrap();
    write_file.write_all(&[1, 2, 3, 4]).unwrap();
    write_file.flush().unwrap();
    {
        let read_handle = directory.open_read(&test_path).unwrap();
        assert_eq!(&*read_handle, &[1u8, 2u8, 3u8, 4u8]);
        // Mapped files can't be deleted on Windows
        if !cfg!(windows) {
            assert!(directory.delete(&test_path).is_ok());
            assert_eq!(&*read_handle, &[1u8, 2u8, 3u8, 4u8]);
        }

        assert!(directory.delete(Path::new("SomeOtherPath")).is_err());
    }

    if cfg!(windows) {
        assert!(directory.delete(&test_path).is_ok());
    }

    assert!(directory.open_read(&test_path).is_err());
    assert!(directory.delete(&test_path).is_err());
}

fn test_watch(directory: &mut dyn Directory) {
    let num_progress: Arc<AtomicUsize> = Default::default();
    let counter: Arc<AtomicUsize> = Default::default();
    let counter_clone = counter.clone();
    let (sender, receiver) = crossbeam::channel::unbounded();
    let watch_callback = Box::new(move || {
        counter_clone.fetch_add(1, SeqCst);
    });
    // This callback is used to synchronize watching in our unit test.
    // We bind it to a variable because the callback is removed when that
    // handle is dropped.
    let watch_handle = directory.watch(watch_callback).unwrap();
    let _progress_listener = directory
        .watch(Box::new(move || {
            let val = num_progress.fetch_add(1, SeqCst);
            let _ = sender.send(val);
        }))
        .unwrap();

    for i in 0..10 {
        assert!(i <= counter.load(SeqCst));
        assert!(directory
            .atomic_write(Path::new("meta.json"), b"random_test_data_2")
            .is_ok());
        assert_eq!(receiver.recv_timeout(Duration::from_millis(500)), Ok(i));
        assert!(i + 1 <= counter.load(SeqCst)); // notify can trigger more than once.
    }
    mem::drop(watch_handle);
    assert!(directory
        .atomic_write(Path::new("meta.json"), b"random_test_data")
        .is_ok());
    assert!(receiver.recv_timeout(Duration::from_millis(500)).is_ok());
    assert!(10 <= counter.load(SeqCst));
}

fn test_lock_non_blocking(directory: &mut dyn Directory) {
    {
        let lock_a_res = directory.acquire_lock(&Lock {
            filepath: PathBuf::from("a.lock"),
            is_blocking: false,
        });
        assert!(lock_a_res.is_ok());
        let lock_b_res = directory.acquire_lock(&Lock {
            filepath: PathBuf::from("b.lock"),
            is_blocking: false,
        });
        assert!(lock_b_res.is_ok());
        let lock_a_res2 = directory.acquire_lock(&Lock {
            filepath: PathBuf::from("a.lock"),
            is_blocking: false,
        });
        assert!(lock_a_res2.is_err());
    }
    let lock_a_res = directory.acquire_lock(&Lock {
        filepath: PathBuf::from("a.lock"),
        is_blocking: false,
    });
    assert!(lock_a_res.is_ok());
}

fn test_lock_blocking(directory: &mut dyn Directory) {
    let lock_a_res = directory.acquire_lock(&Lock {
        filepath: PathBuf::from("a.lock"),
        is_blocking: true,
    });
    assert!(lock_a_res.is_ok());
    let in_thread = Arc::new(AtomicBool::default());
    let in_thread_clone = in_thread.clone();
    let (sender, receiver) = oneshot::channel();
    std::thread::spawn(move || {
        //< lock_a_res is sent to the thread.
        in_thread_clone.store(true, SeqCst);
        let _just_sync = block_on(receiver);
        // explicitely droping lock_a_res. It would have been sufficient to just force it
        // to be part of the move, but the intent seems clearer that way.
        drop(lock_a_res);
    });
    {
        // A non-blocking call should fail, as the thread is running and holding the lock.
        let lock_a_res = directory.acquire_lock(&Lock {
            filepath: PathBuf::from("a.lock"),
            is_blocking: false,
        });
        assert!(lock_a_res.is_err());
    }
    let directory_clone = directory.box_clone();
    let (sender2, receiver2) = oneshot::channel();
    let join_handle = std::thread::spawn(move || {
        assert!(sender2.send(()).is_ok());
        let lock_a_res = directory_clone.acquire_lock(&Lock {
            filepath: PathBuf::from("a.lock"),
            is_blocking: true,
        });
        assert!(in_thread.load(SeqCst));
        assert!(lock_a_res.is_ok());
    });
    assert!(block_on(receiver2).is_ok());
    assert!(sender.send(()).is_ok());
    assert!(join_handle.join().is_ok());
}
<|endoftext|>