hara-native 0.1.13

HAL-free native host runtime and package launcher for Hara
Documentation
use super::*;
use crate::core::Value;
use crate::file::{
    CopyOptions, DeleteOptions, FileError, FileProvider, FileType, MkdirOptions, MoveOptions,
    WriteOptions,
};
use crate::filesystem::{
    FilesystemCallContext, FilesystemCapabilities, FilesystemCapability, FilesystemDescriptor,
    FilesystemEntry, FilesystemEntryPage, FilesystemFuture, FilesystemHandle, FilesystemMutation,
    FilesystemMutationContext, FilesystemPageRequest, IFilesystem,
};
use crate::task::PromiseState;
use std::cell::Cell;
use std::future::Future;
use std::pin::Pin;
use std::rc::Rc;
use std::task::{Context, Poll};

struct OnePending<T> {
    pending: bool,
    value: Option<T>,
}

impl<T: Unpin> Future for OnePending<T> {
    type Output = T;

    fn poll(mut self: Pin<&mut Self>, _context: &mut Context<'_>) -> Poll<Self::Output> {
        if self.pending {
            self.pending = false;
            Poll::Pending
        } else {
            Poll::Ready(self.value.take().expect("future polled after completion"))
        }
    }
}

#[derive(Clone)]
struct FixtureFilesystem {
    closes: Rc<Cell<usize>>,
}

impl FixtureFilesystem {
    fn pending<T: Unpin + 'static>(value: Result<T, FileError>) -> FilesystemFuture<'static, T> {
        Box::pin(OnePending {
            pending: true,
            value: Some(value),
        })
    }
}

impl IFilesystem for FixtureFilesystem {
    fn descriptor(&self) -> FilesystemDescriptor {
        FilesystemDescriptor::new(
            "fixture",
            "redacted fixture",
            true,
            FilesystemCapabilities::new([
                FilesystemCapability::Read,
                FilesystemCapability::Entries,
            ]),
        )
    }

    fn stat<'a>(
        &'a self,
        _context: FilesystemCallContext,
        path: String,
    ) -> FilesystemFuture<'a, FilesystemEntry> {
        Self::pending(Ok(FilesystemEntry {
            name: path.rsplit('/').next().unwrap_or_default().into(),
            path,
            kind: FileType::File,
            size: Some(4),
            modified_at: None,
            id: Some("blob".into()),
            revision: Some("revision".into()),
            capabilities: None,
            extensions: Default::default(),
        }))
    }

    fn read<'a>(
        &'a self,
        _context: FilesystemCallContext,
        _path: String,
    ) -> FilesystemFuture<'a, Vec<u8>> {
        Self::pending(Ok(b"data".to_vec()))
    }

    fn write<'a>(
        &'a self,
        _context: FilesystemCallContext,
        _path: String,
        _bytes: Vec<u8>,
        _options: WriteOptions,
        _mutation: FilesystemMutationContext,
    ) -> FilesystemFuture<'a, FilesystemMutation> {
        Self::pending(Err(FileError::PermissionDenied))
    }

    fn entries_page<'a>(
        &'a self,
        _context: FilesystemCallContext,
        _path: String,
        _request: FilesystemPageRequest,
    ) -> FilesystemFuture<'a, FilesystemEntryPage> {
        Self::pending(Ok(FilesystemEntryPage {
            entries: Vec::new(),
            next_token: None,
        }))
    }

    fn mkdir<'a>(
        &'a self,
        _context: FilesystemCallContext,
        _path: String,
        _options: MkdirOptions,
        _mutation: FilesystemMutationContext,
    ) -> FilesystemFuture<'a, FilesystemMutation> {
        Self::pending(Err(FileError::PermissionDenied))
    }

    fn delete<'a>(
        &'a self,
        _context: FilesystemCallContext,
        _path: String,
        _options: DeleteOptions,
        _mutation: FilesystemMutationContext,
    ) -> FilesystemFuture<'a, FilesystemMutation> {
        Self::pending(Err(FileError::PermissionDenied))
    }

    fn copy<'a>(
        &'a self,
        _context: FilesystemCallContext,
        _source: String,
        _target: String,
        _options: CopyOptions,
        _mutation: FilesystemMutationContext,
    ) -> FilesystemFuture<'a, FilesystemMutation> {
        Self::pending(Err(FileError::PermissionDenied))
    }

    fn move_entry<'a>(
        &'a self,
        _context: FilesystemCallContext,
        _source: String,
        _target: String,
        _options: MoveOptions,
        _mutation: FilesystemMutationContext,
    ) -> FilesystemFuture<'a, FilesystemMutation> {
        Self::pending(Err(FileError::PermissionDenied))
    }

    fn close<'a>(&'a self, _context: FilesystemCallContext) -> FilesystemFuture<'a, ()> {
        self.closes.set(self.closes.get() + 1);
        Self::pending(Ok(()))
    }
}

#[test]
fn kernel_mount_routes_file_effects_through_ifilesystem() {
    let closes = Rc::new(Cell::new(0));
    let handle = FilesystemHandle::new(FixtureFilesystem {
        closes: closes.clone(),
    });
    let mut kernel = SessionKernel::new();
    let mount = kernel.create_provider_filesystem(handle);
    assert_eq!(
        kernel.filesystem_info(mount).unwrap(),
        ("provider", "redacted fixture", 0)
    );

    let child = SessionId::parse("provider-child").unwrap();
    kernel.create_session(child.clone()).unwrap();
    kernel.attach_filesystem(&child, mount).unwrap();
    let provider = kernel
        .session(&child)
        .unwrap()
        .runtime()
        .unwrap()
        .providers
        .file()
        .unwrap();
    let promise = provider.read("/data").unwrap();
    assert!(matches!(promise.state(), PromiseState::Pending));
    assert!(matches!(
        promise.state(),
        PromiseState::Fulfilled(Value::Bytes(bytes)) if bytes == b"data"
    ));
    assert!(kernel.close_filesystem(mount).is_err());

    drop(provider);
    kernel.detach_filesystem(&child).unwrap();
    kernel.close_filesystem(mount).unwrap();
    assert_eq!(closes.get(), 1);
}