pw_libc: Add test for memset

This starts pw_libc; with the intent to eventually have a basic subset
of libc implemented. These will be "CS 101" style implementations, that
aim for correctness and small code size.

The initial CL only adds tests against the system memset() function.

Change-Id: Icd681fff2bf5581c6d44e690bcb5f82a659a03a5
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/26201
Commit-Queue: Keir Mierle <keir@google.com>
Reviewed-by: Ewout van Bekkum <ewout@google.com>
diff --git a/BUILD.gn b/BUILD.gn
index 118f4ca..6040df6 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -259,6 +259,7 @@
       "$dir_pw_hdlc:tests",
       "$dir_pw_hex_dump:tests",
       "$dir_pw_i2c:tests",
+      "$dir_pw_libc:tests",
       "$dir_pw_log:tests",
       "$dir_pw_log_multisink:tests",
       "$dir_pw_log_null:tests",
diff --git a/docs/BUILD.gn b/docs/BUILD.gn
index f0f9aa2..0c99c11 100644
--- a/docs/BUILD.gn
+++ b/docs/BUILD.gn
@@ -85,6 +85,7 @@
     "$dir_pw_interrupt:docs",
     "$dir_pw_interrupt_cortex_m:docs",
     "$dir_pw_kvs:docs",
+    "$dir_pw_libc:docs",
     "$dir_pw_log:docs",
     "$dir_pw_log_basic:docs",
     "$dir_pw_log_multisink:docs",
diff --git a/modules.gni b/modules.gni
index 05e04d5..fa5c03c 100644
--- a/modules.gni
+++ b/modules.gni
@@ -48,6 +48,7 @@
   dir_pw_interrupt = get_path_info("pw_interrupt", "abspath")
   dir_pw_interrupt_cortex_m = get_path_info("pw_interrupt_cortex_m", "abspath")
   dir_pw_kvs = get_path_info("pw_kvs", "abspath")
+  dir_pw_libc = get_path_info("pw_libc", "abspath")
   dir_pw_log = get_path_info("pw_log", "abspath")
   dir_pw_log_basic = get_path_info("pw_log_basic", "abspath")
   dir_pw_log_multisink = get_path_info("pw_log_multisink", "abspath")
diff --git a/pw_libc/BUILD b/pw_libc/BUILD
new file mode 100644
index 0000000..3131801
--- /dev/null
+++ b/pw_libc/BUILD
@@ -0,0 +1,32 @@
+# Copyright 2020 The Pigweed Authors
+#
+# 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
+#
+#     https://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.
+
+load(
+    "//pw_build:pigweed.bzl",
+    "pw_cc_test",
+)
+
+package(default_visibility = ["//visibility:public"])
+
+licenses(["notice"])  # Apache License 2.0
+
+pw_cc_test(
+    name = "memset_test",
+    srcs = [
+        "memset_test.cc",
+    ],
+    deps = [
+        "//pw_unit_test",
+    ],
+)
diff --git a/pw_libc/BUILD.gn b/pw_libc/BUILD.gn
new file mode 100644
index 0000000..9a55cef
--- /dev/null
+++ b/pw_libc/BUILD.gn
@@ -0,0 +1,35 @@
+# Copyright 2019 The Pigweed Authors
+#
+# 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
+#
+#     https://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.
+
+import("//build_overrides/pigweed.gni")
+
+import("$dir_pw_build/target_types.gni")
+import("$dir_pw_docgen/docs.gni")
+import("$dir_pw_unit_test/test.gni")
+
+config("default_config") {
+  include_dirs = [ "public" ]
+}
+
+pw_test_group("tests") {
+  tests = [ ":memset_test" ]
+}
+
+pw_test("memset_test") {
+  sources = [ "memset_test.cc" ]
+}
+
+pw_doc_group("docs") {
+  sources = [ "docs.rst" ]
+}
diff --git a/pw_libc/CMakeLists.txt b/pw_libc/CMakeLists.txt
new file mode 100644
index 0000000..0b1b28d
--- /dev/null
+++ b/pw_libc/CMakeLists.txt
@@ -0,0 +1,16 @@
+# Copyright 2020 The Pigweed Authors
+#
+# 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
+#
+#     https://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.
+
+include($ENV{PW_ROOT}/pw_build/pigweed.cmake)
+
diff --git a/pw_libc/docs.rst b/pw_libc/docs.rst
new file mode 100644
index 0000000..ffee57a
--- /dev/null
+++ b/pw_libc/docs.rst
@@ -0,0 +1,8 @@
+.. _module-pw_libc:
+
+-------
+pw_libc
+-------
+The ``pw_libc`` module provides a restricted subset of libc suitable for some
+microcontroller projects. At this time, only a test suite is provided for
+certain libc functions.
diff --git a/pw_libc/memset_test.cc b/pw_libc/memset_test.cc
new file mode 100644
index 0000000..5ab2a3f
--- /dev/null
+++ b/pw_libc/memset_test.cc
@@ -0,0 +1,115 @@
+// Copyright 2020 The Pigweed Authors
+//
+// 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
+//
+//     https://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.
+//
+// This tests the system installed C standard library version of memset.
+//
+// Note: We have caught real production bugs with these tests. Do not assume
+// your vendor's C library is correct! For standard C functions like memset and
+// memcpy, there are compiler intrisics which assume that the C standard is
+// followed. If the implemention of memset or memcpy does not exactly follow
+// the standard, subtle and hard to track down bugs can be the result.
+
+#include <array>
+#include <cstring>
+#include <numeric>
+
+#include "gtest/gtest.h"
+
+namespace pw {
+namespace {
+
+// From the ISO C standard:
+// http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1256.pdf
+//
+// Section 7.21.6.1: memset(void *s, int c, size_t n)
+//
+//   void* memset(void* buffer,
+//                int character,
+//                size_t num_bytes);
+//
+//   Copy c into the first n bytes of s.
+//   Returns buffer, a copy of the destination pointer.
+//
+
+TEST(Memset, EmptyCase) {
+  std::array<char, 5> arr{'h', 'e', 'l', 'l', 'o'};
+  void* ret = memset(arr.data(), 0, 0);
+
+  // Destination buffer returned.
+  EXPECT_EQ(ret, arr.data());
+
+  // Destination buffer untouched.
+  constexpr std::array<char, 5> kExpected{'h', 'e', 'l', 'l', 'o'};
+  EXPECT_TRUE(
+      std::equal(arr.begin(), arr.end(), kExpected.begin(), kExpected.end()));
+}
+
+TEST(Memset, OneCharacter) {
+  std::array<char, 5> arr{'h', 'e', 'l', 'l', 'o'};
+  void* ret = memset(arr.data(), 0, 1);
+
+  // Ensure the destination buffer is returned.
+  EXPECT_EQ(ret, arr.data());
+
+  // Ensure the destination buffer is untouched.
+  constexpr std::array<char, 5> kExpected{0, 'e', 'l', 'l', 'o'};
+  EXPECT_TRUE(
+      std::equal(arr.begin(), arr.end(), kExpected.begin(), kExpected.end()));
+}
+
+// Now do a detailed case with more values. Span both word sizes and alignments
+// to ensure we hit some edge cases.
+TEST(Memset, MultipleSizesMultipleAlignments) {
+  constexpr int kMaxBytes = 64;
+  std::array<char, kMaxBytes> arr;
+
+  constexpr int kMaxAlignment = 16;
+
+  // Avoid 0 sentinel to prevent interaction with uninitialized memory.
+  constexpr char kSentinel = 3;
+  constexpr char kIotaStart = kSentinel + 7;
+
+  // Try different alignments.
+  for (int alignment = 0; alignment < kMaxAlignment; ++alignment) {
+    // Try different memset sizes.
+    for (int write_size = 0; write_size < (kMaxBytes - kMaxAlignment);
+         ++write_size) {
+      // Fill entire array with incrementing integers; starting above sentinel.
+      std::iota(arr.begin(), arr.end(), kIotaStart);
+
+      // Memset the first write_size bytes, with our sentinel
+      void* write_head = &arr[alignment];
+      const void* ret = memset(write_head, kSentinel, write_size);
+
+      // Check destination buffer returned.
+      EXPECT_EQ(ret, write_head);
+
+      for (int j = 0; j < kMaxBytes; ++j) {
+        if (j < alignment) {
+          // First part of destination buffer untouched; should match iota.
+          EXPECT_EQ(arr[j], kIotaStart + j);
+        } else if (j < alignment + write_size) {
+          // Second part is set to the sentinel value.
+          EXPECT_EQ(arr[j], kSentinel);
+        } else {
+          // Third part is back to the iota content.
+          EXPECT_EQ(arr[j], kIotaStart + j);
+        }
+      }
+    }
+  }
+}
+
+}  // namespace
+}  // namespace pw