From a0182879a61ff154c1e05aa3f61a539eefc6c9aa Mon Sep 17 00:00:00 2001 From: Xinhao Yuan Date: Mon, 14 Sep 2026 09:57:56 -0700 Subject: [PATCH] Add a Rust FuzzTest codelab PiperOrigin-RevId: 981208685 --- rust/codelab/BUILD | 40 ++++++++++++++++++++ rust/codelab/escaping.rs | 61 ++++++++++++++++++++++++++++++ rust/codelab/escaping_test.rs | 71 +++++++++++++++++++++++++++++++++++ 3 files changed, 172 insertions(+) create mode 100644 rust/codelab/BUILD create mode 100644 rust/codelab/escaping.rs create mode 100644 rust/codelab/escaping_test.rs diff --git a/rust/codelab/BUILD b/rust/codelab/BUILD new file mode 100644 index 000000000..3d0d03d3d --- /dev/null +++ b/rust/codelab/BUILD @@ -0,0 +1,40 @@ +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# The Escaping Library example for the Rust FuzzTest Codelab. + +load("@rules_rust//rust:defs.bzl", "rust_library", "rust_test") + +package(default_visibility = ["//visibility:private"]) + +licenses(["notice"]) + +rust_library( + name = "escaping", + srcs = ["escaping.rs"], + crate_root = "escaping.rs", + edition = "2024", +) + +rust_test( + name = "escaping_test", + srcs = ["escaping_test.rs"], + crate_root = "escaping_test.rs", + edition = "2024", + deps = [ + ":escaping", + "@com_google_fuzztest//rust:fuzztest", + "@crate_index//:googletest", + ], +) diff --git a/rust/codelab/escaping.rs b/rust/codelab/escaping.rs new file mode 100644 index 000000000..260cdb0e0 --- /dev/null +++ b/rust/codelab/escaping.rs @@ -0,0 +1,61 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! A tiny escaping library used by the Rust FuzzTest codelab. +//! +//! It converts special characters in a byte string into C-style escape +//! sequences and back. +//! +//! The library intentionally contains two bugs that the codelab's fuzz tests +//! are meant to discover, so please don't "fix" them. + +/// Returns `input` with special characters replaced by C-style escape +/// sequences. +pub fn escape(input: &[u8]) -> Vec { + let mut result = Vec::with_capacity(input.len()); + for &byte in input { + match byte { + b'\n' => result.extend_from_slice(br"\n"), + b'\r' => result.extend_from_slice(br"\r"), + b'\t' => result.extend_from_slice(br"\t"), + b'\\' => result.extend_from_slice(br"\\"), + _ => result.push(byte), + } + } + result +} + +/// Returns `input` with C-style escape sequences replaced by the characters +/// they represent. +/// +/// Unrecognized escape sequences are dropped. +pub fn unescape(input: &[u8]) -> Vec { + let mut result = Vec::with_capacity(input.len()); + let mut i = 0; + while i < input.len() { + if input[i] == b'\\' { + i += 1; + match input[i] { + b'n' => result.push(b'\n'), + b't' => result.push(b'\t'), + b'\\' => result.push(b'\\'), + _ => {} + } + } else { + result.push(input[i]); + } + i += 1; + } + result +} diff --git a/rust/codelab/escaping_test.rs b/rust/codelab/escaping_test.rs new file mode 100644 index 000000000..94ea211f0 --- /dev/null +++ b/rust/codelab/escaping_test.rs @@ -0,0 +1,71 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use escaping::escape; +use escaping::unescape; +use googletest::prelude::*; + +#[gtest] +fn escaping_empty_input_returns_empty_output() { + expect_that!(escape(b""), eq(b"")); +} + +#[gtest] +fn unescaping_empty_input_returns_empty_output() { + expect_that!(unescape(b""), eq(b"")); +} + +#[gtest] +fn escaping_plain_input_is_returned_as_is() { + expect_that!(escape(b"plain text"), eq(b"plain text")); +} + +#[gtest] +fn unescaping_plain_input_is_returned_as_is() { + expect_that!(unescape(b"plain text"), eq(b"plain text")); +} + +#[gtest] +fn escaping_replaces_special_characters() { + expect_that!(escape(b"two\nlines"), eq(br"two\nlines")); + expect_that!(escape(b"back\\slash"), eq(br"back\\slash")); +} + +#[gtest] +fn unescaping_replaces_escape_sequences() { + expect_that!(unescape(br"two\nlines"), eq(b"two\nlines")); + expect_that!(unescape(br"back\\slash"), eq(b"back\\slash")); +} + +// Uncomment the following imports and fuzz tests as you work through the +// codelab. +// +// use fuzztest::domains::arbitrary::Arbitrary; +// use fuzztest::domains::containers::VecOf; +// use fuzztest::fuzztest; + +// #[fuzztest(input = VecOf::new(Arbitrary::::default()))] +// fn unescaping_escaped_input_gives_original(input: Vec) { +// assert_eq!(unescape(&escape(&input)), input); +// } + +// #[fuzztest(input = VecOf::new(Arbitrary::::default()))] +// fn escaping_never_panics(input: Vec) { +// escape(&input); +// } + +// #[fuzztest(input = VecOf::new(Arbitrary::::default()))] +// fn unescaping_never_panics(input: Vec) { +// unescape(&input); +// }