Skip to main content

io_conformance_util/
test_harness.rs

1// Copyright 2021 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use crate::flags::Rights;
6use fidl::endpoints::create_proxy;
7use fidl_fuchsia_component as fcomponent;
8use fidl_fuchsia_component_decl as fdecl;
9use fidl_fuchsia_io as fio;
10use fidl_fuchsia_io_test as io_test;
11
12/// Helper struct for connecting to an io1 test harness and running a conformance test on it.
13pub struct TestHarness {
14    /// FIDL proxy to the io1 test harness.
15    pub proxy: io_test::TestHarnessProxy,
16
17    /// Config for the filesystem.
18    pub config: io_test::HarnessConfig,
19
20    /// All [`io_test::Directory`] rights supported by the filesystem.
21    pub dir_rights: Rights,
22
23    /// All [`io_test::File`] rights supported by the filesystem.
24    pub file_rights: Rights,
25
26    /// All [`io_test::ExecutableFile`] rights supported by the filesystem.
27    pub executable_file_rights: Rights,
28
29    /// All [`io_test::Symlink`] rights supported by the filesystem.
30    pub symlink_rights: Rights,
31}
32
33impl TestHarness {
34    /// Connects to the test harness and returns a `TestHarness` struct.
35    pub async fn new() -> TestHarness {
36        let proxy = connect_to_harness();
37        let config = proxy.get_config().await.expect("Could not get config from proxy");
38
39        // Validate configuration options for consistency, disallow invalid combinations.
40        if config.supports_modify_directory {
41            assert!(
42                config.supports_get_token,
43                "GetToken must be supported for testing Rename/Link!"
44            );
45        }
46        if config.supports_append {
47            assert!(config.supports_mutable_file, "Files supporting append must also be mutable.");
48        }
49        if config.supports_truncate {
50            assert!(
51                config.supports_mutable_file,
52                "Files supporting truncate must also be mutable."
53            );
54        }
55
56        // Generate set of supported open rights for each object type.
57        let dir_rights = Rights::new(get_supported_dir_rights(&config));
58        let file_rights = Rights::new(get_supported_file_rights(&config));
59        let executable_file_rights = Rights::new(fio::Rights::READ_BYTES | fio::Rights::EXECUTE);
60        let symlink_rights = Rights::new(get_supported_symlink_rights(&config));
61
62        TestHarness {
63            proxy,
64            config,
65            dir_rights,
66            file_rights,
67            executable_file_rights,
68            symlink_rights,
69        }
70    }
71
72    /// Creates a [`fio::DirectoryProxy`] with the given root directory structure.
73    pub fn get_directory(
74        &self,
75        entries: Vec<io_test::DirectoryEntry>,
76        flags: fio::Flags,
77    ) -> fio::DirectoryProxy {
78        let contents: Vec<Option<Box<io_test::DirectoryEntry>>> =
79            entries.into_iter().map(|e| Some(Box::new(e))).collect();
80        let (client, server) = create_proxy::<fio::DirectoryMarker>();
81        self.proxy
82            .create_directory(contents, flags, server)
83            .expect("Cannot get directory from test harness");
84        client
85    }
86
87    /// Helper function which gets service directory from the harness as a [`fio::DirectoryProxy`].
88    /// Requires that the harness supports service directories, otherwise will panic.
89    pub async fn open_service_directory(&self) -> fio::DirectoryProxy {
90        assert!(self.config.supports_services);
91        let client_end = self.proxy.open_service_directory().await.unwrap();
92        client_end.into_proxy()
93    }
94
95    /// Returns the abilities [`io_test::File`] objects should have for the harness.
96    pub fn supported_file_abilities(&self) -> fio::Abilities {
97        let mut abilities = fio::Abilities::READ_BYTES | fio::Abilities::GET_ATTRIBUTES;
98        if self.config.supports_mutable_file {
99            abilities |= fio::Abilities::WRITE_BYTES;
100        }
101        if self.supports_mutable_attrs() {
102            abilities |= fio::Abilities::UPDATE_ATTRIBUTES;
103        }
104        abilities
105    }
106
107    /// Returns the abilities [`io_test::Directory`] objects should have for the harness.
108    pub fn supported_dir_abilities(&self) -> fio::Abilities {
109        if self.config.supports_modify_directory {
110            fio::Abilities::GET_ATTRIBUTES
111                | fio::Abilities::UPDATE_ATTRIBUTES
112                | fio::Abilities::ENUMERATE
113                | fio::Abilities::TRAVERSE
114                | fio::Abilities::MODIFY_DIRECTORY
115        } else {
116            fio::Abilities::GET_ATTRIBUTES | fio::Abilities::ENUMERATE | fio::Abilities::TRAVERSE
117        }
118    }
119
120    /// Returns the abilities [`io_test::Symlink`] objects should have for the harness.
121    pub fn supported_symlink_abilities(&self) -> fio::Abilities {
122        let mut abilities = fio::Abilities::GET_ATTRIBUTES;
123        if self.supports_mutable_attrs() {
124            abilities |= fio::Abilities::UPDATE_ATTRIBUTES;
125        }
126        abilities
127    }
128
129    /// Returns true if the harness supports at least one mutable attribute, false otherwise.
130    ///
131    /// *NOTE*: To allow testing both the io1 SetAttrs and io2 UpdateAttributes methods, harnesses
132    /// that support mutable attributes must support [`fio::NodeAttributesQuery::CREATION_TIME`]
133    /// and [`fio::NodeAttributesQuery::MODIFICATION_TIME`].
134    pub fn supports_mutable_attrs(&self) -> bool {
135        supports_mutable_attrs(&self.config)
136    }
137}
138
139fn connect_to_harness() -> io_test::TestHarnessProxy {
140    // Connect to the realm to get access to the outgoing directory for the harness.
141    let (client, server) = zx::Channel::create();
142    fuchsia_component::client::connect_channel_to_protocol::<fcomponent::RealmMarker>(server)
143        .expect("Cannot connect to Realm service");
144    let realm = fcomponent::RealmSynchronousProxy::new(client);
145    // fs_test is the name of the child component defined in the manifest.
146    let child_ref = fdecl::ChildRef { name: "fs_test".to_string(), collection: None };
147    let (client, server) = zx::Channel::create();
148    realm
149        .open_exposed_dir(
150            &child_ref,
151            fidl::endpoints::ServerEnd::<fio::DirectoryMarker>::new(server),
152            zx::MonotonicInstant::INFINITE,
153        )
154        .expect("FIDL error when binding to child in Realm")
155        .expect("Cannot bind to test harness child in Realm");
156
157    let exposed_dir = fio::DirectoryProxy::new(fidl::AsyncChannel::from_channel(client));
158
159    fuchsia_component::client::connect_to_protocol_at_dir_root::<io_test::TestHarnessMarker>(
160        &exposed_dir,
161    )
162    .expect("Cannot connect to test harness protocol")
163}
164
165// Returns the aggregate of all rights that are supported for [`io_test::Directory`] objects.
166// Note that rights are specific to a connection (abilities are properties of the node).
167fn get_supported_dir_rights(config: &io_test::HarnessConfig) -> fio::Rights {
168    fio::R_STAR_DIR
169        | fio::W_STAR_DIR
170        | if config.supports_executable_file { fio::X_STAR_DIR } else { fio::Rights::empty() }
171}
172
173// Returns the aggregate of all rights that are supported for [`io_test::File`] objects.
174// Note that rights are specific to a connection (abilities are properties of the node).
175fn get_supported_file_rights(config: &io_test::HarnessConfig) -> fio::Rights {
176    let mut rights = fio::Rights::READ_BYTES | fio::Rights::GET_ATTRIBUTES;
177    if config.supports_mutable_file {
178        rights |= fio::Rights::WRITE_BYTES;
179    }
180    if supports_mutable_attrs(&config) {
181        rights |= fio::Rights::WRITE_BYTES;
182    }
183    rights
184}
185
186// Returns the aggregate of all rights that are supported for [`io_test::Symlink`] objects.
187// Note that rights are specific to a connection (abilities are properties of the node).
188fn get_supported_symlink_rights(config: &io_test::HarnessConfig) -> fio::Rights {
189    let mut rights = fio::Rights::GET_ATTRIBUTES | fio::Rights::READ_BYTES;
190    if config.supports_mutable_file {
191        rights |= fio::Rights::WRITE_BYTES;
192    }
193    if supports_mutable_attrs(&config) {
194        rights |= fio::Rights::WRITE_BYTES;
195    }
196    if config.supports_executable_file {
197        rights |= fio::Rights::EXECUTE;
198    }
199    rights
200}
201
202// Returns true if the harness supports at least one mutable attribute, false otherwise.
203//
204// *NOTE*: To allow testing both the io1 SetAttrs and io2 UpdateAttributes methods, harnesses
205// that support mutable attributes must support [`fio::NodeAttributesQuery::CREATION_TIME`]
206// and [`fio::NodeAttributesQuery::MODIFICATION_TIME`].
207fn supports_mutable_attrs(config: &io_test::HarnessConfig) -> bool {
208    if !config.supports_mutable_file && !config.supports_modify_directory {
209        return false;
210    }
211    let all_mutable_attrs: fio::NodeAttributesQuery = fio::NodeAttributesQuery::ACCESS_TIME
212        | fio::NodeAttributesQuery::MODIFICATION_TIME
213        | fio::NodeAttributesQuery::CREATION_TIME
214        | fio::NodeAttributesQuery::MODE
215        | fio::NodeAttributesQuery::GID
216        | fio::NodeAttributesQuery::UID
217        | fio::NodeAttributesQuery::RDEV;
218    if config.supported_attributes.intersects(all_mutable_attrs) {
219        assert!(
220            config.supported_attributes.contains(
221                fio::NodeAttributesQuery::CREATION_TIME
222                    | fio::NodeAttributesQuery::MODIFICATION_TIME
223            ),
224            "Harnesses must support at least CREATION_TIME if attributes are mutable."
225        );
226        return true;
227    }
228    false
229}