[libcxx-commits] [libcxx] 7cc72a0 - Implement syncstream (p0053)

Mark de Wever via libcxx-commits libcxx-commits at lists.llvm.org
Wed Nov 8 08:45:26 PST 2023


Author: Mark de Wever
Date: 2023-11-08T17:45:06+01:00
New Revision: 7cc72a0a2ec22855572d96411febd4f2c4ac5a49

URL: https://github.com/llvm/llvm-project/commit/7cc72a0a2ec22855572d96411febd4f2c4ac5a49
DIFF: https://github.com/llvm/llvm-project/commit/7cc72a0a2ec22855572d96411febd4f2c4ac5a49.diff

LOG: Implement syncstream (p0053)

This patch implements `std::basic_syncbuf` and `std::basic_osyncstream` as specified in paper p0053r7. ~~For ease of reviewing I am submitting this patch before submitting a patch for `std::basic_osyncstream`. ~~

~~Please note, this patch is not 100% complete. I plan on adding more tests (see comments), specifically I plan on adding tests for multithreading and synchronization.~~

Edit: I decided that it would be far easier for me to keep track of this and make changes that affect both `std::basic_syncbuf` and `std::basic_osyncstream` if both were in one patch.

The patch was originally written by @zoecarver

Implements
- P0053R7 - C++ Synchronized Buffered Ostream
- LWG-3127 basic_osyncstream::rdbuf needs a const_cast
- LWG-3334 basic_osyncstream move assignment and destruction calls basic_syncbuf::emit() twice
- LWG-3570 basic_osyncstream::emit should be an unformatted output function
- LWG-3867 Should std::basic_osyncstream's move assignment operator be noexcept?

Reviewed By: ldionne, #libc

Differential Revision: https://reviews.llvm.org/D67086

Added: 
    libcxx/include/syncstream
    libcxx/test/std/input.output/syncstream/osyncstream/assign.pass.cpp
    libcxx/test/std/input.output/syncstream/osyncstream/members/emit.pass.cpp
    libcxx/test/std/input.output/syncstream/osyncstream/members/get_wrapped.pass.cpp
    libcxx/test/std/input.output/syncstream/osyncstream/members/rdbuf.pass.cpp
    libcxx/test/std/input.output/syncstream/osyncstream/syncstream.osyncstream.cons/cons.move.pass.cpp
    libcxx/test/std/input.output/syncstream/osyncstream/syncstream.osyncstream.cons/cons.ostream.allocator.pass.cpp
    libcxx/test/std/input.output/syncstream/osyncstream/syncstream.osyncstream.cons/cons.ostream.pass.cpp
    libcxx/test/std/input.output/syncstream/osyncstream/syncstream.osyncstream.cons/cons.pointer.allocator.pass.cpp
    libcxx/test/std/input.output/syncstream/osyncstream/syncstream.osyncstream.cons/cons.pointer.pass.cpp
    libcxx/test/std/input.output/syncstream/osyncstream/thread/basic.pass.cpp
    libcxx/test/std/input.output/syncstream/osyncstream/thread/several_threads.pass.cpp
    libcxx/test/std/input.output/syncstream/osyncstream/types.compile.pass.cpp
    libcxx/test/std/input.output/syncstream/syncbuf/helpers.h
    libcxx/test/std/input.output/syncstream/syncbuf/sputc.pass.cpp
    libcxx/test/std/input.output/syncstream/syncbuf/sputn.pass.cpp
    libcxx/test/std/input.output/syncstream/syncbuf/sync.pass.cpp
    libcxx/test/std/input.output/syncstream/syncbuf/syncstream.syncbuf.assign/assign.pass.cpp
    libcxx/test/std/input.output/syncstream/syncbuf/syncstream.syncbuf.assign/swap.pass.cpp
    libcxx/test/std/input.output/syncstream/syncbuf/syncstream.syncbuf.cons/cons.default.pass.cpp
    libcxx/test/std/input.output/syncstream/syncbuf/syncstream.syncbuf.cons/cons.move.pass.cpp
    libcxx/test/std/input.output/syncstream/syncbuf/syncstream.syncbuf.cons/cons.pointer.allocator.pass.cpp
    libcxx/test/std/input.output/syncstream/syncbuf/syncstream.syncbuf.cons/cons.pointer.pass.cpp
    libcxx/test/std/input.output/syncstream/syncbuf/syncstream.syncbuf.cons/dtor.pass.cpp
    libcxx/test/std/input.output/syncstream/syncbuf/syncstream.syncbuf.members/emit.pass.cpp
    libcxx/test/std/input.output/syncstream/syncbuf/syncstream.syncbuf.members/get_allocator.pass.cpp
    libcxx/test/std/input.output/syncstream/syncbuf/syncstream.syncbuf.members/get_wrapped.pass.cpp
    libcxx/test/std/input.output/syncstream/syncbuf/syncstream.syncbuf.members/set_emit_on_sync.pass.cpp
    libcxx/test/std/input.output/syncstream/syncbuf/syncstream.syncbuf.special/swap.pass.cpp
    libcxx/test/std/input.output/syncstream/syncbuf/types.compile.pass.cpp
    libcxx/test/std/language.support/support.limits/support.limits.general/syncstream.version.compile.pass.cpp

Modified: 
    libcxx/docs/FeatureTestMacroTable.rst
    libcxx/docs/ReleaseNotes/18.rst
    libcxx/docs/Status/Cxx20Issues.csv
    libcxx/docs/Status/Cxx20Papers.csv
    libcxx/docs/Status/Cxx23Issues.csv
    libcxx/include/CMakeLists.txt
    libcxx/include/__config
    libcxx/include/__std_clang_module
    libcxx/include/iosfwd
    libcxx/include/map
    libcxx/include/streambuf
    libcxx/include/version
    libcxx/modules/std.cppm.in
    libcxx/modules/std/iosfwd.inc
    libcxx/modules/std/syncstream.inc
    libcxx/test/libcxx/experimental/fexperimental-library.compile.pass.cpp
    libcxx/test/libcxx/transitive_includes/cxx03.csv
    libcxx/test/libcxx/transitive_includes/cxx11.csv
    libcxx/test/libcxx/transitive_includes/cxx14.csv
    libcxx/test/libcxx/transitive_includes/cxx17.csv
    libcxx/test/libcxx/transitive_includes/cxx20.csv
    libcxx/test/libcxx/transitive_includes/cxx23.csv
    libcxx/test/libcxx/transitive_includes/cxx26.csv
    libcxx/test/std/language.support/support.limits/support.limits.general/version.version.compile.pass.cpp
    libcxx/test/support/test_allocator.h
    libcxx/utils/generate_feature_test_macro_components.py
    libcxx/utils/libcxx/header_information.py
    libcxx/utils/libcxx/test/params.py

Removed: 
    


################################################################################
diff  --git a/libcxx/docs/FeatureTestMacroTable.rst b/libcxx/docs/FeatureTestMacroTable.rst
index 10afd64e0a39469..42bf6eb46212d07 100644
--- a/libcxx/docs/FeatureTestMacroTable.rst
+++ b/libcxx/docs/FeatureTestMacroTable.rst
@@ -286,7 +286,7 @@ Status
     --------------------------------------------------- -----------------
     ``__cpp_lib_string_view``                           ``201803L``
     --------------------------------------------------- -----------------
-    ``__cpp_lib_syncbuf``                               *unimplemented*
+    ``__cpp_lib_syncbuf``                               ``201803L``
     --------------------------------------------------- -----------------
     ``__cpp_lib_three_way_comparison``                  *unimplemented*
     --------------------------------------------------- -----------------

diff  --git a/libcxx/docs/ReleaseNotes/18.rst b/libcxx/docs/ReleaseNotes/18.rst
index 41c3f7cc455f185..cf70d45e97886df 100644
--- a/libcxx/docs/ReleaseNotes/18.rst
+++ b/libcxx/docs/ReleaseNotes/18.rst
@@ -47,6 +47,7 @@ Implemented Papers
 - P2443R1 - ``views::chunk_by``
 - P2538R1 - ADL-proof ``std::projected``
 - P2614R2 - Deprecate ``numeric_limits::has_denorm``
+- P0053R7 - C++ Synchronized Buffered Ostream (in the experimental library)
 
 
 Improvements and New Features

diff  --git a/libcxx/docs/Status/Cxx20Issues.csv b/libcxx/docs/Status/Cxx20Issues.csv
index 6d27e2406fb0e89..08b8bda9705e0d3 100644
--- a/libcxx/docs/Status/Cxx20Issues.csv
+++ b/libcxx/docs/Status/Cxx20Issues.csv
@@ -114,7 +114,7 @@
 "`3096 <https://wg21.link/LWG3096>`__","``path::lexically_relative``\  is confused by trailing slashes","San Diego","|Complete|",""
 "`3116 <https://wg21.link/LWG3116>`__","``OUTERMOST_ALLOC_TRAITS``\  needs ``remove_reference_t``\ ","San Diego","",""
 "`3122 <https://wg21.link/LWG3122>`__","``__cpp_lib_chrono_udls``\  was accidentally dropped","San Diego","|Complete|",""
-"`3127 <https://wg21.link/LWG3127>`__","``basic_osyncstream::rdbuf``\  needs a ``const_cast``\ ","San Diego","",""
+"`3127 <https://wg21.link/LWG3127>`__","``basic_osyncstream::rdbuf``\ needs a ``const_cast``\ ","San Diego","|Complete|","18.0"
 "`3128 <https://wg21.link/LWG3128>`__","``strstream::rdbuf``\  needs a ``const_cast``\ ","San Diego","|Nothing To Do|",""
 "`3129 <https://wg21.link/LWG3129>`__","``regex_token_iterator``\  constructor uses wrong pointer arithmetic","San Diego","",""
 "`3130 <https://wg21.link/LWG3130>`__","|sect|\ [input.output] needs many ``addressof``\ ","San Diego","",""
@@ -254,7 +254,7 @@
 "`3330 <https://wg21.link/LWG3330>`__","Include ``<compare>``\  from most library headers","Prague","|Complete|","13.0","|spaceship|"
 "`3331 <https://wg21.link/LWG3331>`__","Define ``totally_ordered/_with``\  in terms of ``partially-ordered-with``\ ","Prague","|Complete|","13.0"
 "`3332 <https://wg21.link/LWG3332>`__","Issue in |sect|\ [time.format]","Prague","|Complete|","16.0","|chrono| |format|"
-"`3334 <https://wg21.link/LWG3334>`__","``basic_osyncstream``\  move assignment and destruction calls ``basic_syncbuf::emit()``\  twice","Prague","",""
+"`3334 <https://wg21.link/LWG3334>`__","``basic_osyncstream``\  move assignment and destruction calls ``basic_syncbuf::emit()``\ twice","Prague","|Complete|","18.0"
 "`3335 <https://wg21.link/LWG3335>`__","Resolve C++20 NB comments US 273 and GB 274","Prague","|Complete|","15.0","|ranges|"
 "`3338 <https://wg21.link/LWG3338>`__","Rename ``default_constructible``\  to ``default_initializable``\ ","Prague","|Complete|","13.0"
 "`3340 <https://wg21.link/LWG3340>`__","Formatting functions should throw on argument/format string mismatch in |sect|\ [format.functions]","Prague","|Complete|","14.0","|format|"

diff  --git a/libcxx/docs/Status/Cxx20Papers.csv b/libcxx/docs/Status/Cxx20Papers.csv
index dd6fcf9a7583ce3..107b2bca110f692 100644
--- a/libcxx/docs/Status/Cxx20Papers.csv
+++ b/libcxx/docs/Status/Cxx20Papers.csv
@@ -3,7 +3,7 @@
 "`P0674R1 <https://wg21.link/P0674R1>`__","LWG","Extending make_shared to Support Arrays","Toronto","|Complete|","15.0"
 "","","","","","",""
 "`P0020R6 <https://wg21.link/P0020R6>`__","LWG","Floating Point Atomic","Albuquerque","",""
-"`P0053R7 <https://wg21.link/P0053R7>`__","LWG","C++ Synchronized Buffered Ostream","Albuquerque","",""
+"`P0053R7 <https://wg21.link/P0053R7>`__","LWG","C++ Synchronized Buffered Ostream","Albuquerque","|Complete|","18.0"
 "`P0202R3 <https://wg21.link/P0202R3>`__","LWG","Add constexpr modifiers to functions in <algorithm> and <utility> Headers","Albuquerque","|Complete|","12.0"
 "`P0415R1 <https://wg21.link/P0415R1>`__","LWG","Constexpr for ``std::complex``\ ","Albuquerque","|Complete|","16.0"
 "`P0439R0 <https://wg21.link/P0439R0>`__","LWG","Make ``std::memory_order``\  a scoped enumeration","Albuquerque","|Complete|",""

diff  --git a/libcxx/docs/Status/Cxx23Issues.csv b/libcxx/docs/Status/Cxx23Issues.csv
index 8be1ff588cf3b5c..fbe48123c1febf0 100644
--- a/libcxx/docs/Status/Cxx23Issues.csv
+++ b/libcxx/docs/Status/Cxx23Issues.csv
@@ -122,7 +122,7 @@
 `3566 <https://wg21.link/LWG3566>`__,"Constraint recursion for ``operator<=>(optional<T>, U)``","October 2021","|Complete|","17.0","|spaceship|"
 `3567 <https://wg21.link/LWG3567>`__,"Formatting move-only iterators take two","October 2021","|Complete|","16.0","|format| |ranges|"
 `3568 <https://wg21.link/LWG3568>`__,"``basic_istream_view`` needs to initialize ``value_``","October 2021","|Complete|","16.0","|ranges|"
-`3570 <https://wg21.link/LWG3570>`__,"``basic_osyncstream::emit`` should be an unformatted output function","October 2021","",""
+`3570 <https://wg21.link/LWG3570>`__,"``basic_osyncstream::emit`` should be an unformatted output function","October 2021","|Complete|","18.0"
 `3571 <https://wg21.link/LWG3571>`__,"``flush_emit`` should set ``badbit`` if the ``emit`` call fails","October 2021","",""
 `3572 <https://wg21.link/LWG3572>`__,"``copyable-box`` should be fully ``constexpr``","October 2021","|Complete|","14.0","|ranges|"
 `3573 <https://wg21.link/LWG3573>`__,"Missing Throws element for ``basic_string_view(It begin, End end)``","October 2021","|Complete|","14.0"
@@ -275,7 +275,7 @@
 "`3857 <https://wg21.link/LWG3857>`__","``basic_string_view`` should allow explicit conversion when only traits vary","February 2023","|Complete|","17.0",""
 "`3860 <https://wg21.link/LWG3860>`__","``range_common_reference_t`` is missing","February 2023","|Complete|","17.0","|ranges|"
 "`3866 <https://wg21.link/LWG3866>`__","Bad Mandates for ``expected::transform_error`` overloads","February 2023","|Complete|","17.0",""
-"`3867 <https://wg21.link/LWG3867>`__","Should ``std::basic_osyncstream``'s move assignment operator be ``noexcept``?","February 2023","","",""
+"`3867 <https://wg21.link/LWG3867>`__","Should ``std::basic_osyncstream``'s move assignment operator be ``noexcept``?","February 2023","|Complete|","18.0",""
 "`3441 <https://wg21.link/LWG3441>`__","Misleading note about calls to customization points","February 2023","","",""
 "`3622 <https://wg21.link/LWG3622>`__","Misspecified transitivity of equivalence in ยง[unord.req.general]","February 2023","","",""
 "`3631 <https://wg21.link/LWG3631>`__","``basic_format_arg(T&&)`` should use ``remove_cvref_t<T>`` throughout","February 2023","|Complete|","17.0",""

diff  --git a/libcxx/include/CMakeLists.txt b/libcxx/include/CMakeLists.txt
index 5e8c1700ee4cdad..889d7fedbf2965f 100644
--- a/libcxx/include/CMakeLists.txt
+++ b/libcxx/include/CMakeLists.txt
@@ -1000,6 +1000,7 @@ set(files
   string.h
   string_view
   strstream
+  syncstream
   system_error
   tgmath.h
   thread

diff  --git a/libcxx/include/__config b/libcxx/include/__config
index b9b20fbfb271e8e..28802be542e446c 100644
--- a/libcxx/include/__config
+++ b/libcxx/include/__config
@@ -452,6 +452,7 @@
 #    define _LIBCPP_HAS_NO_INCOMPLETE_PSTL
 #    define _LIBCPP_HAS_NO_EXPERIMENTAL_STOP_TOKEN
 #    define _LIBCPP_HAS_NO_INCOMPLETE_TZDB
+#    define _LIBCPP_HAS_NO_EXPERIMENTAL_SYNCSTREAM
 #  endif
 
 // Need to detect which libc we're using if we're on Linux.

diff  --git a/libcxx/include/__std_clang_module b/libcxx/include/__std_clang_module
index c4bfe8259c74a3a..10ac3ccb8f329f4 100644
--- a/libcxx/include/__std_clang_module
+++ b/libcxx/include/__std_clang_module
@@ -200,6 +200,9 @@
 #if !defined(_LIBCPP_HAS_NO_LOCALIZATION)
 #  include <strstream>
 #endif
+#if !defined(_LIBCPP_HAS_NO_LOCALIZATION)
+#  include <syncstream>
+#endif
 #include <system_error>
 #include <tgmath.h>
 #if !defined(_LIBCPP_HAS_NO_THREADS)

diff  --git a/libcxx/include/iosfwd b/libcxx/include/iosfwd
index dbbf7fdaf6de4b2..80c0c27179b8f1e 100644
--- a/libcxx/include/iosfwd
+++ b/libcxx/include/iosfwd
@@ -90,6 +90,18 @@ using u8streampos = fpos<char_traits<char8_t>::state_type>; // C++20
 using u16streampos = fpos<char_traits<char16_t>::state_type>;
 using u32streampos = fpos<char_traits<char32_t>::state_type>;
 
+template <class charT, class traits = char_traits<charT>, class Allocator = allocator<charT>>
+    class basic_syncbuf;                          // C++20
+
+using syncbuf = basic_syncbuf<char>;              // C++20
+using wsyncbuf = basic_syncbuf<wchar_t>;          // C++20
+
+template <class charT, class traits = char_traits<charT>, class Allocator = allocator<charT>>
+    class basic_osyncstream;                      // C++20
+
+using osyncstream = basic_osyncstream<char>;      // C++20
+using wosyncstream = basic_osyncstream<wchar_t>;  // C++20
+
 }  // std
 
 */
@@ -130,6 +142,26 @@ typedef fpos<mbstate_t>    u8streampos;
 typedef fpos<mbstate_t>    u16streampos;
 typedef fpos<mbstate_t>    u32streampos;
 
+#if _LIBCPP_STD_VER >= 20
+
+template <class _CharT, class _Traits = char_traits<_CharT>, class _Allocator = allocator<_CharT>>
+class basic_syncbuf;
+
+using syncbuf  = basic_syncbuf<char>;
+#ifndef _LIBCPP_HAS_NO_WIDE_CHARACTERS
+using wsyncbuf = basic_syncbuf<wchar_t>;
+#endif
+
+template <class _CharT, class _Traits = char_traits<_CharT>, class _Allocator = allocator<_CharT>>
+class basic_osyncstream;
+
+using osyncstream  = basic_osyncstream<char>;
+#ifndef _LIBCPP_HAS_NO_WIDE_CHARACTERS
+using wosyncstream = basic_osyncstream<wchar_t>;
+#endif
+
+#endif // _LIBCPP_STD_VER >=20
+
 // Include other forward declarations here
 template <class _Tp, class _Alloc = allocator<_Tp> >
 class _LIBCPP_TEMPLATE_VIS vector;

diff  --git a/libcxx/include/map b/libcxx/include/map
index 04cf2d313422327..1b0f1d1c792b90e 100644
--- a/libcxx/include/map
+++ b/libcxx/include/map
@@ -1700,6 +1700,8 @@ template <class _Key, class _Tp, class _Compare, class _Allocator>
 _Tp&
 map<_Key, _Tp, _Compare, _Allocator>::operator[](key_type&& __k)
 {
+    // TODO investigate this clang-tidy warning.
+    // NOLINTNEXTLINE(bugprone-use-after-move)
     return __tree_.__emplace_unique_key_args(__k,
         _VSTD::piecewise_construct,
         _VSTD::forward_as_tuple(_VSTD::move(__k)),

diff  --git a/libcxx/include/streambuf b/libcxx/include/streambuf
index 9d94e42f37fe20e..6d6fd143988204e 100644
--- a/libcxx/include/streambuf
+++ b/libcxx/include/streambuf
@@ -110,6 +110,7 @@ protected:
 #include <__assert> // all public C++ headers provide the assertion handler
 #include <__config>
 #include <__fwd/streambuf.h>
+#include <climits>
 #include <ios>
 #include <iosfwd>
 #include <version>

diff  --git a/libcxx/include/syncstream b/libcxx/include/syncstream
new file mode 100644
index 000000000000000..c54e8ce9f54ce47
--- /dev/null
+++ b/libcxx/include/syncstream
@@ -0,0 +1,510 @@
+// -*- C++ -*-
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef _LIBCPP_SYNCSTREAM
+#define _LIBCPP_SYNCSTREAM
+
+/*
+    syncstream synopsis
+
+#include <ostream>  // see [ostream.syn]
+
+namespace std {
+    template<class charT, class traits, class Allocator>
+    class basic_syncbuf;
+
+    // [syncstream.syncbuf.special], specialized algorithms
+    template<class charT, class traits, class Allocator>
+      void swap(basic_syncbuf<charT, traits, Allocator>&,
+                basic_syncbuf<charT, traits, Allocator>&);
+
+    using syncbuf = basic_syncbuf<char>;
+    using wsyncbuf = basic_syncbuf<wchar_t>;
+
+    template<class charT, class traits, class Allocator>
+    class basic_osyncstream;
+
+    using osyncstream = basic_osyncstream<char>;
+    using wosyncstream = basic_osyncstream<wchar_t>;
+
+    template<class charT, class traits, class Allocator>
+    class basic_syncbuf : public basic_streambuf<charT, traits> {
+    public:
+        using char_type      = charT;
+        using int_type       = typename traits::int_type;
+        using pos_type       = typename traits::pos_type;
+        using off_type       = typename traits::off_type;
+        using traits_type    = traits;
+        using allocator_type = Allocator;
+
+        using streambuf_type = basic_streambuf<charT, traits>;
+
+        // [syncstream.syncbuf.cons], construction and destruction
+        explicit basic_syncbuf(streambuf_type* obuf = nullptr)
+          : basic_syncbuf(obuf, Allocator()) {}
+        basic_syncbuf(streambuf_type*, const Allocator&);
+        basic_syncbuf(basic_syncbuf&&);
+        ~basic_syncbuf();
+
+        // [syncstream.syncbuf.assign], assignment and swap
+        basic_syncbuf& operator=(basic_syncbuf&&);
+        void swap(basic_syncbuf&);
+
+        // [syncstream.syncbuf.members], member functions
+        bool emit();
+        streambuf_type* get_wrapped() const noexcept;
+        allocator_type get_allocator() const noexcept;
+        void set_emit_on_sync(bool) noexcept;
+
+    protected:
+        // [syncstream.syncbuf.virtuals], overridden virtual functions
+        int sync() override;
+
+    private:
+        streambuf_type* wrapped;    // exposition only
+        bool emit_on_sync{};        // exposition only
+    };
+
+    // [syncstream.syncbuf.special], specialized algorithms
+    template<class charT, class traits, class Allocator>
+    void swap(basic_syncbuf<charT, traits, Allocator>&,
+              basic_syncbuf<charT, traits, Allocator>&);
+
+    template<class charT, class traits, class Allocator>
+    class basic_osyncstream : public basic_ostream<charT, traits> {
+    public:
+        using char_type   = charT;
+        using int_type    = typename traits::int_type;
+        using pos_type    = typename traits::pos_type;
+        using off_type    = typename traits::off_type;
+        using traits_type = traits;
+
+        using allocator_type = Allocator;
+        using streambuf_type = basic_streambuf<charT, traits>;
+        using syncbuf_type   = basic_syncbuf<charT, traits, Allocator>;
+
+        // [syncstream.osyncstream.cons], construction and destruction
+        basic_osyncstream(streambuf_type*, const Allocator&);
+        explicit basic_osyncstream(streambuf_type* obuf)
+          : basic_osyncstream(obuf, Allocator()) {}
+        basic_osyncstream(basic_ostream<charT, traits>& os, const Allocator& allocator)
+          : basic_osyncstream(os.rdbuf(), allocator) {}
+        explicit basic_osyncstream(basic_ostream<charT, traits>& os)
+          : basic_osyncstream(os, Allocator()) {}
+        basic_osyncstream(basic_osyncstream&&) noexcept;
+        ~basic_osyncstream();
+
+        // [syncstream.osyncstream.assign], assignment
+        basic_osyncstream& operator=(basic_osyncstream&&);
+
+        // [syncstream.osyncstream.members], member functions
+        void emit();
+        streambuf_type* get_wrapped() const noexcept;
+        syncbuf_type* rdbuf() const noexcept { return const_cast<syncbuf_type*>(addressof(sb)); }
+
+    private:
+        syncbuf_type sb;    // exposition only
+    };
+}
+
+*/
+
+#include <__config>
+#include <__utility/move.h>
+#include <iosfwd> // required for declaration of default arguments
+#include <string>
+
+#ifndef _LIBCPP_HAS_NO_THREADS
+#  include <map>
+#  include <mutex>
+#  include <shared_mutex>
+#endif
+
+// standard-mandated includes
+
+// [syncstream.syn]
+#include <ostream>
+
+#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
+#  pragma GCC system_header
+#endif
+
+_LIBCPP_PUSH_MACROS
+#include <__undef_macros>
+
+_LIBCPP_BEGIN_NAMESPACE_STD
+
+#if _LIBCPP_STD_VER >= 20 && !defined(_LIBCPP_HAS_NO_EXPERIMENTAL_SYNCSTREAM)
+
+// [syncstream.syncbuf.overview]/1
+//   Class template basic_syncbuf stores character data written to it,
+//   known as the associated output, into internal buffers allocated
+//   using the object's allocator. The associated output is transferred
+//   to the wrapped stream buffer object *wrapped when emit() is called
+//   or when the basic_syncbuf object is destroyed. Such transfers are
+//   atomic with respect to transfers by other basic_syncbuf objects
+//   with the same wrapped stream buffer object.
+//
+// This helper singleton is used to implement the required
+// synchronisation guarantees.
+#  ifndef _LIBCPP_HAS_NO_THREADS
+class __wrapped_streambuf_mutex {
+  _LIBCPP_HIDE_FROM_ABI __wrapped_streambuf_mutex() = default;
+
+public:
+  __wrapped_streambuf_mutex(const __wrapped_streambuf_mutex&)            = delete;
+  __wrapped_streambuf_mutex& operator=(const __wrapped_streambuf_mutex&) = delete;
+
+  _LIBCPP_HIDE_FROM_ABI void __inc_reference([[maybe_unused]] void* __ptr) {
+    _LIBCPP_ASSERT_INTERNAL(__ptr != nullptr, "non-wrapped streambufs are never written to");
+    unique_lock __lock{__mutex_};
+    ++__lut_[reinterpret_cast<uintptr_t>(__ptr)].__count;
+  }
+
+  // pre: __ptr is in __lut_
+  _LIBCPP_HIDE_FROM_ABI void __dec_reference([[maybe_unused]] void* __ptr) noexcept {
+    unique_lock __lock{__mutex_};
+
+    auto __it = __get_it(__ptr);
+    if (__it->second.__count == 1)
+      __lut_.erase(__it);
+    else
+      --__it->second.__count;
+  }
+
+  // TODO
+  // This function causes emit() aquire two mutexes:
+  // - __mutex_ shared
+  // _ __get_it(__ptr)->second.__mutex exclusive
+  //
+  // Instead store a pointer to __get_it(__ptr)->second.__mutex when
+  // calling __inc_reference.
+  //
+  // pre: __ptr is in __lut_
+  [[nodiscard]] _LIBCPP_HIDE_FROM_ABI lock_guard<mutex> __get_lock([[maybe_unused]] void* __ptr) noexcept {
+    shared_lock __lock{__mutex_};
+    return lock_guard{__get_it(__ptr)->second.__mutex};
+  }
+
+  // This function is used for testing.
+  //
+  // It is allowed to call this function with a non-registered pointer.
+  [[nodiscard]] _LIBCPP_HIDE_FROM_ABI size_t __get_count([[maybe_unused]] void* __ptr) noexcept {
+    _LIBCPP_ASSERT_INTERNAL(__ptr != nullptr, "non-wrapped streambufs are never written to");
+    shared_lock __lock{__mutex_};
+
+    auto __it = __lut_.find(reinterpret_cast<uintptr_t>(__ptr));
+    return __it != __lut_.end() ? __it->second.__count : 0;
+  }
+
+  [[nodiscard]] static _LIBCPP_HIDE_FROM_ABI __wrapped_streambuf_mutex& __instance() noexcept {
+    static __wrapped_streambuf_mutex __result;
+    return __result;
+  }
+
+private:
+  struct __value {
+    mutex __mutex;
+    size_t __count{0};
+  };
+
+  shared_mutex __mutex_;
+  map<uintptr_t, __value> __lut_;
+
+  [[nodiscard]] _LIBCPP_HIDE_FROM_ABI map<uintptr_t, __value>::iterator __get_it(void* __ptr) noexcept {
+    _LIBCPP_ASSERT_INTERNAL(__ptr != nullptr, "non-wrapped streambufs are never written to");
+
+    auto __it = __lut_.find(reinterpret_cast<uintptr_t>(__ptr));
+    _LIBCPP_ASSERT_INTERNAL(__it != __lut_.end(), "using a wrapped streambuf that has not been registered");
+    _LIBCPP_ASSERT_INTERNAL(__it->second.__count >= 1, "found an inactive streambuf wrapper");
+    return __it;
+  }
+};
+#  endif // _LIBCPP_HAS_NO_THREADS
+
+// basic_syncbuf
+
+// The class uses a basic_string<_CharT, _Traits, _Allocator> as
+// internal buffer. Per [syncstream.syncbuf.cons]/4
+//   Remarks: A copy of allocator is used to allocate memory for
+//   internal buffers holding the associated output.
+//
+// Therefore the allocator used in the constructor is passed to the
+// basic_string. The class does not keep a copy of this allocator.
+template <class _CharT, class _Traits, class _Allocator>
+class _LIBCPP_TEMPLATE_VIS basic_syncbuf : public basic_streambuf<_CharT, _Traits> {
+public:
+  using char_type      = _CharT;
+  using traits_type    = _Traits;
+  using int_type       = typename traits_type::int_type;
+  using pos_type       = typename traits_type::pos_type;
+  using off_type       = typename traits_type::off_type;
+  using allocator_type = _Allocator;
+
+  using streambuf_type = basic_streambuf<_CharT, _Traits>;
+
+  // [syncstream.syncbuf.cons], construction and destruction
+
+  _LIBCPP_HIDE_FROM_ABI explicit basic_syncbuf(streambuf_type* __obuf = nullptr)
+      : basic_syncbuf(__obuf, _Allocator()) {}
+
+  _LIBCPP_HIDE_FROM_ABI basic_syncbuf(streambuf_type* __obuf, _Allocator const& __alloc)
+      : __wrapped_(__obuf), __str_(__alloc) {
+    __inc_reference();
+  }
+
+  _LIBCPP_HIDE_FROM_ABI basic_syncbuf(basic_syncbuf&& __other)
+      : __wrapped_(__other.get_wrapped()), __str_(std::move(__other.__str_)), __emit_on_sync_(__other.__emit_on_sync_) {
+    __move_common(__other);
+  }
+
+  _LIBCPP_HIDE_FROM_ABI ~basic_syncbuf() {
+#  ifndef _LIBCPP_HAS_NO_EXCEPTIONS
+    try {
+#  endif // _LIBCPP_HAS_NO_EXCEPTIONS
+      emit();
+#  ifndef _LIBCPP_HAS_NO_EXCEPTIONS
+    } catch (...) {
+    }
+#  endif // _LIBCPP_HAS_NO_EXCEPTIONS
+    __dec_reference();
+  }
+
+  // [syncstream.syncbuf.assign], assignment and swap
+
+  _LIBCPP_HIDE_FROM_ABI basic_syncbuf& operator=(basic_syncbuf&& __other) {
+    // The function is specified to call emit. This call should
+    // propagate the exception thrown.
+    emit();
+    __dec_reference();
+
+    __wrapped_      = __other.get_wrapped();
+    __str_          = std::move(__other.__str_);
+    __emit_on_sync_ = __other.__emit_on_sync_;
+
+    __move_common(__other);
+
+    return *this;
+  }
+
+  _LIBCPP_HIDE_FROM_ABI void swap(basic_syncbuf& __other) {
+    _LIBCPP_ASSERT_COMPATIBLE_ALLOCATOR(
+        allocator_traits<_Allocator>::propagate_on_container_swap::value || get_allocator() == __other.get_allocator(),
+        "violates the mandated swap precondition");
+
+    basic_syncbuf __tmp(std::move(__other));
+    __other = std::move(*this);
+    *this   = std::move(__tmp);
+  }
+
+  // [syncstream.syncbuf.members], member functions
+
+  _LIBCPP_HIDE_FROM_ABI bool emit() { return emit(false); }
+
+  _LIBCPP_HIDE_FROM_ABI streambuf_type* get_wrapped() const noexcept { return __wrapped_; }
+
+  _LIBCPP_HIDE_FROM_ABI allocator_type get_allocator() const noexcept { return __str_.get_allocator(); }
+
+  _LIBCPP_HIDE_FROM_ABI void set_emit_on_sync(bool __b) noexcept { __emit_on_sync_ = __b; }
+
+protected:
+  // [syncstream.syncbuf.virtuals], overridden virtual functions
+
+  _LIBCPP_HIDE_FROM_ABI_VIRTUAL
+  int sync() override {
+    if (__emit_on_sync_ && !emit(true))
+      return -1;
+    return 0;
+  }
+
+  _LIBCPP_HIDE_FROM_ABI_VIRTUAL
+  int_type overflow(int_type __c = traits_type::eof()) override {
+    if (traits_type::eq_int_type(__c, traits_type::eof()))
+      return traits_type::not_eof(__c);
+
+    if (this->pptr() == this->epptr()) {
+#  ifndef _LIBCPP_HAS_NO_EXCEPTIONS
+      try {
+#  endif
+        size_t __size = __str_.size();
+        __str_.resize(__str_.capacity() + 1);
+        _LIBCPP_ASSERT_INTERNAL(__str_.size() > __size, "the buffer hasn't grown");
+
+        char_type* __p = static_cast<char_type*>(__str_.data());
+        this->setp(__p, __p + __str_.size());
+        this->pbump(__size);
+
+#  ifndef _LIBCPP_HAS_NO_EXCEPTIONS
+      } catch (...) {
+        return traits_type::eof();
+      }
+#  endif
+    }
+
+    return this->sputc(traits_type::to_char_type(__c));
+  }
+
+private:
+  streambuf_type* __wrapped_;
+
+  // TODO Use a more generic buffer.
+  // That buffer should be light with almost no additional headers. Then
+  // it can be use here, the __retarget_buffer, and place that use
+  // the now deprecated get_temporary_buffer
+
+  basic_string<_CharT, _Traits, _Allocator> __str_;
+  bool __emit_on_sync_{false};
+
+  _LIBCPP_HIDE_FROM_ABI bool emit(bool __flush) {
+    if (!__wrapped_)
+      return false;
+
+#  ifndef _LIBCPP_HAS_NO_THREADS
+    lock_guard<mutex> __lock = __wrapped_streambuf_mutex::__instance().__get_lock(__wrapped_);
+#  endif
+
+    bool __result = true;
+    if (this->pptr() != this->pbase()) {
+      _LIBCPP_ASSERT_INTERNAL(this->pbase() && this->pptr() && this->epptr(), "all put area pointers shold be valid");
+
+      // The __str_ does not know how much of its buffer is used. This
+      // information is extracted from the information of the base class.
+      __result &= (__wrapped_->sputn(this->pbase(), this->pptr() - this->pbase()) != -1);
+      // Clears the buffer, but keeps the contents (and) size of the
+      // internal buffer.
+      this->setp(this->pbase(), this->epptr());
+    }
+
+    if (__flush)
+      __result &= (__wrapped_->pubsync() != -1);
+
+    return __result;
+  }
+
+  _LIBCPP_HIDE_FROM_ABI void __move_common(basic_syncbuf& __other) {
+    // Adjust the put area pointers to our buffer.
+    char_type* __p = static_cast<char_type*>(__str_.data());
+    this->setp(__p, __p + __str_.size());
+    this->pbump(__other.pptr() - __other.pbase());
+
+    // Clear __other_ so the destructor will act as a NOP.
+    __other.setp(nullptr, nullptr);
+    __other.__wrapped_ = nullptr;
+  }
+
+  _LIBCPP_HIDE_FROM_ABI void __inc_reference() {
+#  ifndef _LIBCPP_HAS_NO_THREADS
+    if (__wrapped_)
+      __wrapped_streambuf_mutex::__instance().__inc_reference(__wrapped_);
+#  endif
+  }
+
+  _LIBCPP_HIDE_FROM_ABI void __dec_reference() noexcept {
+#  ifndef _LIBCPP_HAS_NO_THREADS
+    if (__wrapped_)
+      __wrapped_streambuf_mutex::__instance().__dec_reference(__wrapped_);
+#  endif
+  }
+};
+
+using std::syncbuf;
+#  ifndef _LIBCPP_HAS_NO_WIDE_CHARACTERS
+using std::wsyncbuf;
+#  endif
+
+// [syncstream.syncbuf.special], specialized algorithms
+template <class _CharT, class _Traits, class _Allocator>
+_LIBCPP_HIDE_FROM_ABI void
+swap(basic_syncbuf<_CharT, _Traits, _Allocator>& __lhs, basic_syncbuf<_CharT, _Traits, _Allocator>& __rhs) {
+  __lhs.swap(__rhs);
+}
+
+// basic_osyncstream
+
+template <class _CharT, class _Traits, class _Allocator>
+class _LIBCPP_TEMPLATE_VIS basic_osyncstream : public basic_ostream<_CharT, _Traits> {
+public:
+  using char_type   = _CharT;
+  using traits_type = _Traits;
+  using int_type    = typename traits_type::int_type;
+  using pos_type    = typename traits_type::pos_type;
+  using off_type    = typename traits_type::off_type;
+
+  using allocator_type = _Allocator;
+  using streambuf_type = basic_streambuf<char_type, traits_type>;
+  using syncbuf_type   = basic_syncbuf<char_type, traits_type, allocator_type>;
+
+  // [syncstream.osyncstream.cons], construction and destruction
+
+  _LIBCPP_HIDE_FROM_ABI basic_osyncstream(streambuf_type* __obuf, allocator_type const& __alloc)
+      : basic_ostream<_CharT, _Traits>(std::addressof(__sb_)), __sb_(__obuf, __alloc) {}
+
+  _LIBCPP_HIDE_FROM_ABI explicit basic_osyncstream(streambuf_type* __obuf)
+      : basic_osyncstream(__obuf, allocator_type()) {}
+
+  _LIBCPP_HIDE_FROM_ABI basic_osyncstream(basic_ostream<char_type, traits_type>& __os, allocator_type const& __alloc)
+      : basic_osyncstream(__os.rdbuf(), __alloc) {}
+
+  _LIBCPP_HIDE_FROM_ABI explicit basic_osyncstream(basic_ostream<char_type, traits_type>& __os)
+      : basic_osyncstream(__os, allocator_type()) {}
+
+  _LIBCPP_HIDE_FROM_ABI basic_osyncstream(basic_osyncstream&& __other) noexcept
+      : basic_ostream<_CharT, _Traits>(std::addressof(__sb_)), __sb_(std::move(__other.__sb_)) {
+    this->set_rdbuf(std::addressof(__sb_));
+  }
+
+  // [syncstream.osyncstream.assign], assignment
+
+  _LIBCPP_HIDE_FROM_ABI basic_osyncstream& operator=(basic_osyncstream&& __other) = default;
+
+  // [syncstream.osyncstream.members], member functions
+
+  _LIBCPP_HIDE_FROM_ABI void emit() {
+    // The basic_ostream::put places the sentry in a try
+    // catch, this does not match the wording of the standard
+    // [ostream.unformatted]
+    // TODO validate other unformatted output functions.
+    typename basic_ostream<char_type, traits_type>::sentry __s(*this);
+    if (__s) {
+#  ifndef _LIBCPP_HAS_NO_EXCEPTIONS
+      try {
+#  endif
+
+        if (__sb_.emit() == false)
+          this->setstate(ios::badbit);
+#  ifndef _LIBCPP_HAS_NO_EXCEPTIONS
+      } catch (...) {
+        this->__set_badbit_and_consider_rethrow();
+      }
+#  endif
+    }
+  }
+
+  _LIBCPP_HIDE_FROM_ABI streambuf_type* get_wrapped() const noexcept { return __sb_.get_wrapped(); }
+
+  _LIBCPP_HIDE_FROM_ABI syncbuf_type* rdbuf() const noexcept {
+    return const_cast<syncbuf_type*>(std::addressof(__sb_));
+  }
+
+private:
+  syncbuf_type __sb_;
+};
+
+using std::osyncstream;
+#  ifndef _LIBCPP_HAS_NO_WIDE_CHARACTERS
+using std::wosyncstream;
+#  endif
+
+#endif // _LIBCPP_STD_VER >= 20 && !defined(_LIBCPP_HAS_NO_EXPERIMENTAL_SYNCSTREAM)
+
+_LIBCPP_END_NAMESPACE_STD
+
+_LIBCPP_POP_MACROS
+
+#endif // _LIBCPP_SYNCSTREAM

diff  --git a/libcxx/include/version b/libcxx/include/version
index 3c8e1a132e87f85..ec0cb0813492383 100644
--- a/libcxx/include/version
+++ b/libcxx/include/version
@@ -401,7 +401,9 @@ __cpp_lib_within_lifetime                               202306L <type_traits>
 # define __cpp_lib_starts_ends_with                     201711L
 # undef  __cpp_lib_string_view
 # define __cpp_lib_string_view                          201803L
-// # define __cpp_lib_syncbuf                              201803L
+# if !defined(_LIBCPP_HAS_NO_EXPERIMENTAL_SYNCSTREAM)
+#   define __cpp_lib_syncbuf                            201803L
+# endif
 // # define __cpp_lib_three_way_comparison                 201907L
 # define __cpp_lib_to_address                           201711L
 # define __cpp_lib_to_array                             201907L

diff  --git a/libcxx/modules/std.cppm.in b/libcxx/modules/std.cppm.in
index 65cec804ff122ae..09ea531e172e131 100644
--- a/libcxx/modules/std.cppm.in
+++ b/libcxx/modules/std.cppm.in
@@ -148,6 +148,9 @@ module;
 #if !defined(_LIBCPP_HAS_NO_LOCALIZATION)
 #  include <strstream>
 #endif
+#if !defined(_LIBCPP_HAS_NO_LOCALIZATION)
+#  include <syncstream>
+#endif
 #include <system_error>
 #if !defined(_LIBCPP_HAS_NO_THREADS)
 #  include <thread>
@@ -189,9 +192,6 @@ module;
 #if __has_include(<stdfloat>)
 #  error "update the header information for <stdfloat> in libcxx/utils/generate_std_cppm_in.py"
 #endif //   __has_include(<stdfloat>)
-#if __has_include(<syncstream>)
-#  error "update the header information for <syncstream> in libcxx/utils/generate_std_cppm_in.py"
-#endif //   __has_include(<syncstream>)
 #if __has_include(<text_encoding>)
 #  error "update the header information for <text_encoding> in libcxx/utils/generate_std_cppm_in.py"
 #endif //   __has_include(<text_encoding>)

diff  --git a/libcxx/modules/std/iosfwd.inc b/libcxx/modules/std/iosfwd.inc
index aba7f1e419803e3..ec8b434ca0c51b7 100644
--- a/libcxx/modules/std/iosfwd.inc
+++ b/libcxx/modules/std/iosfwd.inc
@@ -16,8 +16,19 @@ export namespace std {
   using std::u32streampos;
   using std::u8streampos;
 
+  using std::basic_osyncstream;
+  using std::basic_syncbuf;
+
   using std::istreambuf_iterator;
   using std::ostreambuf_iterator;
 
+  using std::osyncstream;
+  using std::syncbuf;
+
+#ifndef _LIBCPP_HAS_NO_WIDE_CHARACTERS
+  using std::wosyncstream;
+  using std::wsyncbuf;
+#endif
+
   using std::fpos;
 } // namespace std

diff  --git a/libcxx/modules/std/syncstream.inc b/libcxx/modules/std/syncstream.inc
index df9b679e38842b5..e3fb391b67c6364 100644
--- a/libcxx/modules/std/syncstream.inc
+++ b/libcxx/modules/std/syncstream.inc
@@ -8,7 +8,7 @@
 //===----------------------------------------------------------------------===//
 
 export namespace std {
-#if 0
+#if !defined(_LIBCPP_HAS_NO_LOCALIZATION) && !defined(_LIBCPP_HAS_NO_EXPERIMENTAL_SYNCSTREAM)
   using std::basic_syncbuf;
 
   // [syncstream.syncbuf.special], specialized algorithms
@@ -24,5 +24,5 @@ export namespace std {
 #  ifndef _LIBCPP_HAS_NO_WIDE_CHARACTERS
   using std::wosyncstream;
 #  endif
-#endif
+#endif // !defined(_LIBCPP_HAS_NO_LOCALIZATION) && !defined(_LIBCPP_HAS_NO_EXPERIMENTAL_SYNCSTREAM)
 } // namespace std

diff  --git a/libcxx/test/libcxx/experimental/fexperimental-library.compile.pass.cpp b/libcxx/test/libcxx/experimental/fexperimental-library.compile.pass.cpp
index 92062eb98889e0c..7c98ff1c1d566c6 100644
--- a/libcxx/test/libcxx/experimental/fexperimental-library.compile.pass.cpp
+++ b/libcxx/test/libcxx/experimental/fexperimental-library.compile.pass.cpp
@@ -27,3 +27,7 @@
 #ifdef _LIBCPP_HAS_NO_INCOMPLETE_TZDB
 #  error "-fexperimental-library should enable the chrono TZDB"
 #endif
+
+#ifdef _LIBCPP_HAS_NO_EXPERIMENTAL_SYNCSTREAM
+#  error "-fexperimental-library should enable the syncstream header"
+#endif

diff  --git a/libcxx/test/libcxx/transitive_includes/cxx03.csv b/libcxx/test/libcxx/transitive_includes/cxx03.csv
index 0e7523b5420d43d..65eae36a4c6c6e6 100644
--- a/libcxx/test/libcxx/transitive_includes/cxx03.csv
+++ b/libcxx/test/libcxx/transitive_includes/cxx03.csv
@@ -813,6 +813,7 @@ stop_token limits
 stop_token ratio
 stop_token type_traits
 stop_token version
+streambuf climits
 streambuf cstdint
 streambuf ios
 streambuf iosfwd
@@ -859,6 +860,13 @@ string_view version
 strstream istream
 strstream ostream
 strstream version
+syncstream cstddef
+syncstream iosfwd
+syncstream map
+syncstream mutex
+syncstream ostream
+syncstream shared_mutex
+syncstream string
 system_error cerrno
 system_error compare
 system_error cstddef

diff  --git a/libcxx/test/libcxx/transitive_includes/cxx11.csv b/libcxx/test/libcxx/transitive_includes/cxx11.csv
index 08e7786d9b19435..a6c574e1d4b64bd 100644
--- a/libcxx/test/libcxx/transitive_includes/cxx11.csv
+++ b/libcxx/test/libcxx/transitive_includes/cxx11.csv
@@ -819,6 +819,7 @@ stop_token limits
 stop_token ratio
 stop_token type_traits
 stop_token version
+streambuf climits
 streambuf cstdint
 streambuf ios
 streambuf iosfwd
@@ -865,6 +866,13 @@ string_view version
 strstream istream
 strstream ostream
 strstream version
+syncstream cstddef
+syncstream iosfwd
+syncstream map
+syncstream mutex
+syncstream ostream
+syncstream shared_mutex
+syncstream string
 system_error cerrno
 system_error compare
 system_error cstddef

diff  --git a/libcxx/test/libcxx/transitive_includes/cxx14.csv b/libcxx/test/libcxx/transitive_includes/cxx14.csv
index 37a6e5e853d27e7..9021c91ee2aed05 100644
--- a/libcxx/test/libcxx/transitive_includes/cxx14.csv
+++ b/libcxx/test/libcxx/transitive_includes/cxx14.csv
@@ -821,6 +821,7 @@ stop_token limits
 stop_token ratio
 stop_token type_traits
 stop_token version
+streambuf climits
 streambuf cstdint
 streambuf ios
 streambuf iosfwd
@@ -867,6 +868,13 @@ string_view version
 strstream istream
 strstream ostream
 strstream version
+syncstream cstddef
+syncstream iosfwd
+syncstream map
+syncstream mutex
+syncstream ostream
+syncstream shared_mutex
+syncstream string
 system_error cerrno
 system_error compare
 system_error cstddef

diff  --git a/libcxx/test/libcxx/transitive_includes/cxx17.csv b/libcxx/test/libcxx/transitive_includes/cxx17.csv
index 37a6e5e853d27e7..9021c91ee2aed05 100644
--- a/libcxx/test/libcxx/transitive_includes/cxx17.csv
+++ b/libcxx/test/libcxx/transitive_includes/cxx17.csv
@@ -821,6 +821,7 @@ stop_token limits
 stop_token ratio
 stop_token type_traits
 stop_token version
+streambuf climits
 streambuf cstdint
 streambuf ios
 streambuf iosfwd
@@ -867,6 +868,13 @@ string_view version
 strstream istream
 strstream ostream
 strstream version
+syncstream cstddef
+syncstream iosfwd
+syncstream map
+syncstream mutex
+syncstream ostream
+syncstream shared_mutex
+syncstream string
 system_error cerrno
 system_error compare
 system_error cstddef

diff  --git a/libcxx/test/libcxx/transitive_includes/cxx20.csv b/libcxx/test/libcxx/transitive_includes/cxx20.csv
index 5c9638fd467f214..c047702b445994e 100644
--- a/libcxx/test/libcxx/transitive_includes/cxx20.csv
+++ b/libcxx/test/libcxx/transitive_includes/cxx20.csv
@@ -826,6 +826,7 @@ stop_token limits
 stop_token ratio
 stop_token type_traits
 stop_token version
+streambuf climits
 streambuf cstdint
 streambuf ios
 streambuf iosfwd
@@ -872,6 +873,13 @@ string_view version
 strstream istream
 strstream ostream
 strstream version
+syncstream cstddef
+syncstream iosfwd
+syncstream map
+syncstream mutex
+syncstream ostream
+syncstream shared_mutex
+syncstream string
 system_error cerrno
 system_error compare
 system_error cstddef

diff  --git a/libcxx/test/libcxx/transitive_includes/cxx23.csv b/libcxx/test/libcxx/transitive_includes/cxx23.csv
index e1d489391c8de6c..a0ed2e290ffd92a 100644
--- a/libcxx/test/libcxx/transitive_includes/cxx23.csv
+++ b/libcxx/test/libcxx/transitive_includes/cxx23.csv
@@ -564,6 +564,7 @@ stop_token ctime
 stop_token limits
 stop_token ratio
 stop_token version
+streambuf climits
 streambuf ios
 streambuf iosfwd
 streambuf version
@@ -597,6 +598,13 @@ string_view version
 strstream istream
 strstream ostream
 strstream version
+syncstream cstddef
+syncstream iosfwd
+syncstream map
+syncstream mutex
+syncstream ostream
+syncstream shared_mutex
+syncstream string
 system_error cerrno
 system_error compare
 system_error cstddef

diff  --git a/libcxx/test/libcxx/transitive_includes/cxx26.csv b/libcxx/test/libcxx/transitive_includes/cxx26.csv
index e1d489391c8de6c..a0ed2e290ffd92a 100644
--- a/libcxx/test/libcxx/transitive_includes/cxx26.csv
+++ b/libcxx/test/libcxx/transitive_includes/cxx26.csv
@@ -564,6 +564,7 @@ stop_token ctime
 stop_token limits
 stop_token ratio
 stop_token version
+streambuf climits
 streambuf ios
 streambuf iosfwd
 streambuf version
@@ -597,6 +598,13 @@ string_view version
 strstream istream
 strstream ostream
 strstream version
+syncstream cstddef
+syncstream iosfwd
+syncstream map
+syncstream mutex
+syncstream ostream
+syncstream shared_mutex
+syncstream string
 system_error cerrno
 system_error compare
 system_error cstddef

diff  --git a/libcxx/test/std/input.output/syncstream/osyncstream/assign.pass.cpp b/libcxx/test/std/input.output/syncstream/osyncstream/assign.pass.cpp
new file mode 100644
index 000000000000000..14a9f10e24293f9
--- /dev/null
+++ b/libcxx/test/std/input.output/syncstream/osyncstream/assign.pass.cpp
@@ -0,0 +1,101 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17
+// UNSUPPORTED: no-localization
+// UNSUPPORTED: libcpp-has-no-experimental-syncstream
+
+// <syncstream>
+
+// template <class charT, class traits, class Allocator>
+// class basic_osyncstream;
+
+// basic_osyncstream& operator=(basic_osyncstream&& rhs);
+
+#include <syncstream>
+#include <sstream>
+#include <cassert>
+
+#include "test_macros.h"
+#include "test_allocator.h"
+
+template <class CharT, bool propagate>
+static void test() {
+  using Traits    = std::char_traits<CharT>;
+  using Allocator = std::conditional_t<propagate, other_allocator<CharT>, test_allocator<CharT>>;
+  static_assert(std::allocator_traits<Allocator>::propagate_on_container_move_assignment::value == propagate);
+
+  using OS = std::basic_osyncstream<CharT, Traits, Allocator>;
+
+  std::basic_stringbuf<CharT, Traits, Allocator> base1;
+  std::basic_stringbuf<CharT, Traits, Allocator> base2;
+
+  {
+    OS out1{&base1, Allocator{42}};
+    assert(out1.get_wrapped() == &base1);
+
+    typename OS::syncbuf_type* sb1 = out1.rdbuf();
+    assert(sb1->get_wrapped() == &base1);
+    assert(sb1->get_allocator().get_data() == 42);
+
+    out1 << CharT('A');
+
+    static_assert(!noexcept(out1.operator=(std::move(out1)))); // LWG-3867
+    OS out2{&base2, Allocator{99}};
+
+    out2 << CharT('Z');
+
+    // Validate the data is still in the syncbuf and not in the stringbuf.
+    assert(base1.str().empty());
+    assert(base2.str().empty());
+
+    out2 = std::move(out1);
+
+    // Since sb2 is overwritten by the move its data should be in its stringbuf.
+    assert(base1.str().empty());
+    assert(base2.str().size() == 1);
+    assert(base2.str()[0] == CharT('Z'));
+
+    assert(out2.get_wrapped() == &base1);
+
+    typename OS::syncbuf_type* sb2 = out2.rdbuf();
+    assert(sb2->get_wrapped() == &base1);
+    if constexpr (std::allocator_traits<Allocator>::propagate_on_container_move_assignment::value)
+      assert(sb2->get_allocator().get_data() == 42);
+    else
+      assert(sb2->get_allocator().get_data() == 99);
+
+    assert(out1.get_wrapped() == nullptr);
+    assert(sb1->get_wrapped() == nullptr);
+
+    // The data written to 2 will be stored in sb1. The write happens after the destruction.
+    out2 << CharT('B');
+    assert(base1.str().empty());
+  }
+
+  assert(base1.str().size() == 2);
+  assert(base1.str()[0] == CharT('A'));
+  assert(base1.str()[1] == CharT('B'));
+  assert(base2.str().size() == 1);
+  assert(base2.str()[0] == CharT('Z'));
+}
+
+template <class CharT>
+static void test() {
+  test<CharT, true>();
+  test<CharT, false>();
+}
+
+int main(int, char**) {
+  test<char>();
+#ifndef TEST_HAS_NO_WIDE_CHARACTERS
+  test<wchar_t>();
+#endif
+
+  return 0;
+}

diff  --git a/libcxx/test/std/input.output/syncstream/osyncstream/members/emit.pass.cpp b/libcxx/test/std/input.output/syncstream/osyncstream/members/emit.pass.cpp
new file mode 100644
index 000000000000000..bf0c221b63ac33f
--- /dev/null
+++ b/libcxx/test/std/input.output/syncstream/osyncstream/members/emit.pass.cpp
@@ -0,0 +1,45 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17
+// UNSUPPORTED: no-localization
+// UNSUPPORTED: libcpp-has-no-experimental-syncstream
+
+// <syncstream>
+
+// template <class charT, class traits, class Allocator>
+// class basic_osyncstream;
+
+// void emit();
+
+#include <syncstream>
+#include <sstream>
+#include <cassert>
+
+#include "test_macros.h"
+
+template <class CharT>
+void test() {
+  using OS = std::basic_osyncstream<CharT>;
+  using SS = std::basic_ostringstream<CharT>;
+  CharT c  = 'f';
+
+  SS ss;
+  OS out(ss);
+  out << c;
+  assert(ss.str().empty());
+  out.emit();
+  assert(ss.str()[0] == c);
+}
+
+int main(int, char**) {
+  test<char>();
+#ifndef TEST_HAS_NO_WIDE_CHARACTERS
+  test<wchar_t>();
+#endif
+}

diff  --git a/libcxx/test/std/input.output/syncstream/osyncstream/members/get_wrapped.pass.cpp b/libcxx/test/std/input.output/syncstream/osyncstream/members/get_wrapped.pass.cpp
new file mode 100644
index 000000000000000..d57acf22302f672
--- /dev/null
+++ b/libcxx/test/std/input.output/syncstream/osyncstream/members/get_wrapped.pass.cpp
@@ -0,0 +1,41 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17
+// UNSUPPORTED: no-localization
+// UNSUPPORTED: libcpp-has-no-experimental-syncstream
+
+// <syncstream>
+
+// template <class charT, class traits, class Allocator>
+// class basic_osyncstream;
+
+// streambuf_type* get_wrapped() const noexcept;
+
+#include <syncstream>
+#include <sstream>
+#include <cassert>
+
+#include "test_macros.h"
+
+template <class CharT>
+void test() {
+  std::basic_stringbuf<CharT> base;
+  const std::basic_osyncstream<CharT> out{&base};
+  assert(out.get_wrapped() == &base);
+  ASSERT_NOEXCEPT(out.get_wrapped());
+}
+
+int main(int, char**) {
+  test<char>();
+#ifndef TEST_HAS_NO_WIDE_CHARACTERS
+  test<wchar_t>();
+#endif
+
+  return 0;
+}

diff  --git a/libcxx/test/std/input.output/syncstream/osyncstream/members/rdbuf.pass.cpp b/libcxx/test/std/input.output/syncstream/osyncstream/members/rdbuf.pass.cpp
new file mode 100644
index 000000000000000..975b6e1c4a1d20f
--- /dev/null
+++ b/libcxx/test/std/input.output/syncstream/osyncstream/members/rdbuf.pass.cpp
@@ -0,0 +1,40 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17
+// UNSUPPORTED: no-localization
+// UNSUPPORTED: libcpp-has-no-experimental-syncstream
+
+// <syncstream>
+
+// template <class charT, class traits, class Allocator>
+// class basic_osyncstream;
+
+// syncbuf_type* rdbuf() const noexcept
+
+#include <syncstream>
+#include <sstream>
+#include <cassert>
+
+#include "test_macros.h"
+
+template <class CharT>
+void test() {
+  const std::basic_osyncstream<CharT> out{nullptr};
+  assert(out.rdbuf() != nullptr);
+  ASSERT_NOEXCEPT(out.rdbuf());
+}
+
+int main(int, char**) {
+  test<char>();
+#ifndef TEST_HAS_NO_WIDE_CHARACTERS
+  test<wchar_t>();
+#endif
+
+  return 0;
+}

diff  --git a/libcxx/test/std/input.output/syncstream/osyncstream/syncstream.osyncstream.cons/cons.move.pass.cpp b/libcxx/test/std/input.output/syncstream/osyncstream/syncstream.osyncstream.cons/cons.move.pass.cpp
new file mode 100644
index 000000000000000..7fcbe78e5a812ad
--- /dev/null
+++ b/libcxx/test/std/input.output/syncstream/osyncstream/syncstream.osyncstream.cons/cons.move.pass.cpp
@@ -0,0 +1,135 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17
+// UNSUPPORTED: no-localization
+// UNSUPPORTED: libcpp-has-no-experimental-syncstream
+
+// <syncstream>
+
+// template <class charT, class traits, class Allocator>
+// class basic_osyncstream;
+
+// basic_osyncstream(basic_osyncstream&&) noexcept;
+
+// TODO Why is this noexcept?
+// Does the reasoning for https://cplusplus.github.io/LWG/issue3867 not hold true here?
+
+#include <cassert>
+#include <sstream>
+#include <syncstream>
+
+#include "test_macros.h"
+#include "constexpr_char_traits.h"
+#include "test_allocator.h"
+
+template <class CharT>
+void test() {
+  {
+    using OS = std::basic_osyncstream<CharT>;
+    using W  = std::basic_syncbuf<CharT>;
+
+    const std::allocator<CharT> alloc;
+    {
+      OS os = {OS{nullptr, alloc}};
+      assert(os.get_wrapped() == nullptr);
+      assert(os.rdbuf()->get_wrapped() == nullptr);
+      assert(os.rdbuf()->get_allocator() == alloc);
+      ASSERT_NOEXCEPT(OS{std::move(os)});
+    }
+    {
+      W w;
+#if defined(_LIBCPP_VERSION) && !defined(TEST_HAS_NO_THREADS)
+      assert(std::__wrapped_streambuf_mutex::__instance().__get_count(&w) == 0);
+#endif
+      {
+        OS os = {OS{&w, alloc}};
+#if defined(_LIBCPP_VERSION) && !defined(TEST_HAS_NO_THREADS)
+        assert(std::__wrapped_streambuf_mutex::__instance().__get_count(&w) == 1);
+#endif
+        assert(os.get_wrapped() == &w);
+        assert(os.rdbuf()->get_wrapped() == &w);
+        assert(os.rdbuf()->get_allocator() == alloc);
+      }
+#if defined(_LIBCPP_VERSION) && !defined(TEST_HAS_NO_THREADS)
+      assert(std::__wrapped_streambuf_mutex::__instance().__get_count(&w) == 0);
+#endif
+    }
+  }
+
+  {
+    using OS = std::basic_osyncstream<CharT, constexpr_char_traits<CharT>>;
+    using W  = std::basic_stringbuf<CharT, constexpr_char_traits<CharT>>;
+
+    const std::allocator<CharT> alloc;
+    {
+      OS os = {OS{nullptr, alloc}};
+      assert(os.get_wrapped() == nullptr);
+      assert(os.rdbuf()->get_wrapped() == nullptr);
+      assert(os.rdbuf()->get_allocator() == alloc);
+    }
+    {
+      W w;
+#if defined(_LIBCPP_VERSION) && !defined(TEST_HAS_NO_THREADS)
+      assert(std::__wrapped_streambuf_mutex::__instance().__get_count(&w) == 0);
+#endif
+      {
+        OS os = {OS{&w, alloc}};
+#if defined(_LIBCPP_VERSION) && !defined(TEST_HAS_NO_THREADS)
+        assert(std::__wrapped_streambuf_mutex::__instance().__get_count(&w) == 1);
+#endif
+        assert(os.get_wrapped() == &w);
+        assert(os.rdbuf()->get_wrapped() == &w);
+        assert(os.rdbuf()->get_allocator() == alloc);
+      }
+#if defined(_LIBCPP_VERSION) && !defined(TEST_HAS_NO_THREADS)
+      assert(std::__wrapped_streambuf_mutex::__instance().__get_count(&w) == 0);
+#endif
+    }
+  }
+
+  {
+    using OS = std::basic_osyncstream<CharT, constexpr_char_traits<CharT>, test_allocator<CharT>>;
+    using W  = std::basic_stringbuf<CharT, constexpr_char_traits<CharT>, test_allocator<CharT>>;
+
+    const test_allocator<CharT> alloc;
+    {
+      OS os = {OS{nullptr, alloc}};
+      assert(os.get_wrapped() == nullptr);
+      assert(os.rdbuf()->get_wrapped() == nullptr);
+      assert(os.rdbuf()->get_allocator() == alloc);
+    }
+    {
+      W w;
+#if defined(_LIBCPP_VERSION) && !defined(TEST_HAS_NO_THREADS)
+      assert(std::__wrapped_streambuf_mutex::__instance().__get_count(&w) == 0);
+#endif
+      {
+        OS os = {OS{&w, alloc}};
+#if defined(_LIBCPP_VERSION) && !defined(TEST_HAS_NO_THREADS)
+        assert(std::__wrapped_streambuf_mutex::__instance().__get_count(&w) == 1);
+#endif
+        assert(os.get_wrapped() == &w);
+        assert(os.rdbuf()->get_wrapped() == &w);
+        assert(os.rdbuf()->get_allocator() == alloc);
+      }
+#if defined(_LIBCPP_VERSION) && !defined(TEST_HAS_NO_THREADS)
+      assert(std::__wrapped_streambuf_mutex::__instance().__get_count(&w) == 0);
+#endif
+    }
+  }
+}
+
+int main(int, char**) {
+  test<char>();
+#ifndef TEST_HAS_NO_WIDE_CHARACTERS
+  test<wchar_t>();
+#endif
+
+  return 0;
+}

diff  --git a/libcxx/test/std/input.output/syncstream/osyncstream/syncstream.osyncstream.cons/cons.ostream.allocator.pass.cpp b/libcxx/test/std/input.output/syncstream/osyncstream/syncstream.osyncstream.cons/cons.ostream.allocator.pass.cpp
new file mode 100644
index 000000000000000..271d2ad24e3cd19
--- /dev/null
+++ b/libcxx/test/std/input.output/syncstream/osyncstream/syncstream.osyncstream.cons/cons.ostream.allocator.pass.cpp
@@ -0,0 +1,114 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17
+// UNSUPPORTED: no-localization
+// UNSUPPORTED: libcpp-has-no-experimental-syncstream
+
+// <syncstream>
+
+// template <class charT, class traits, class Allocator>
+// class basic_osyncstream;
+
+// basic_osyncstream(basic_ostream& os, const Allocator& allocator);
+
+#include <cassert>
+#include <concepts>
+#include <syncstream>
+#include <sstream>
+
+#include "test_macros.h"
+#include "constexpr_char_traits.h"
+#include "test_allocator.h"
+
+template <class CharT>
+void test() {
+  {
+    using OS = std::basic_osyncstream<CharT>;
+    using W  = std::basic_ostringstream<CharT>;
+
+    const std::allocator<CharT> alloc;
+
+    {
+      W w;
+#if defined(_LIBCPP_VERSION) && !defined(TEST_HAS_NO_THREADS)
+      assert(std::__wrapped_streambuf_mutex::__instance().__get_count(w.rdbuf()) == 0);
+#endif
+      {
+        OS os = {w, alloc};
+#if defined(_LIBCPP_VERSION) && !defined(TEST_HAS_NO_THREADS)
+        assert(std::__wrapped_streambuf_mutex::__instance().__get_count(w.rdbuf()) == 1);
+#endif
+        assert(os.get_wrapped() == w.rdbuf());
+        assert(os.rdbuf()->get_wrapped() == w.rdbuf());
+        assert(os.rdbuf()->get_allocator() == alloc);
+      }
+#if defined(_LIBCPP_VERSION) && !defined(TEST_HAS_NO_THREADS)
+      assert(std::__wrapped_streambuf_mutex::__instance().__get_count(w.rdbuf()) == 0);
+#endif
+    }
+  }
+  {
+    using OS = std::basic_osyncstream<CharT, constexpr_char_traits<CharT>>;
+    using W  = std::basic_ostringstream<CharT, constexpr_char_traits<CharT>>;
+
+    const std::allocator<CharT> alloc;
+    {
+      W w;
+#if defined(_LIBCPP_VERSION) && !defined(TEST_HAS_NO_THREADS)
+      assert(std::__wrapped_streambuf_mutex::__instance().__get_count(w.rdbuf()) == 0);
+#endif
+      {
+        OS os = {w.rdbuf(), alloc};
+#if defined(_LIBCPP_VERSION) && !defined(TEST_HAS_NO_THREADS)
+        assert(std::__wrapped_streambuf_mutex::__instance().__get_count(w.rdbuf()) == 1);
+#endif
+        assert(os.get_wrapped() == w.rdbuf());
+        assert(os.rdbuf()->get_wrapped() == w.rdbuf());
+        assert(os.rdbuf()->get_allocator() == alloc);
+      }
+#if defined(_LIBCPP_VERSION) && !defined(TEST_HAS_NO_THREADS)
+      assert(std::__wrapped_streambuf_mutex::__instance().__get_count(w.rdbuf()) == 0);
+#endif
+    }
+  }
+
+  {
+    using OS = std::basic_osyncstream<CharT, constexpr_char_traits<CharT>, test_allocator<CharT>>;
+    using W  = std::basic_ostringstream<CharT, constexpr_char_traits<CharT>, test_allocator<CharT>>;
+
+    const test_allocator<CharT> alloc;
+    {
+      W w;
+#if defined(_LIBCPP_VERSION) && !defined(TEST_HAS_NO_THREADS)
+      assert(std::__wrapped_streambuf_mutex::__instance().__get_count(w.rdbuf()) == 0);
+#endif
+      {
+        OS os = {w.rdbuf(), alloc};
+#if defined(_LIBCPP_VERSION) && !defined(TEST_HAS_NO_THREADS)
+        assert(std::__wrapped_streambuf_mutex::__instance().__get_count(w.rdbuf()) == 1);
+#endif
+        assert(os.get_wrapped() == w.rdbuf());
+        assert(os.rdbuf()->get_wrapped() == w.rdbuf());
+        assert(os.rdbuf()->get_allocator() == alloc);
+      }
+#if defined(_LIBCPP_VERSION) && !defined(TEST_HAS_NO_THREADS)
+      assert(std::__wrapped_streambuf_mutex::__instance().__get_count(w.rdbuf()) == 0);
+#endif
+    }
+  }
+}
+
+int main(int, char**) {
+  test<char>();
+#ifndef TEST_HAS_NO_WIDE_CHARACTERS
+  test<wchar_t>();
+#endif
+
+  return 0;
+}

diff  --git a/libcxx/test/std/input.output/syncstream/osyncstream/syncstream.osyncstream.cons/cons.ostream.pass.cpp b/libcxx/test/std/input.output/syncstream/osyncstream/syncstream.osyncstream.cons/cons.ostream.pass.cpp
new file mode 100644
index 000000000000000..411473c053d9b03
--- /dev/null
+++ b/libcxx/test/std/input.output/syncstream/osyncstream/syncstream.osyncstream.cons/cons.ostream.pass.cpp
@@ -0,0 +1,120 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17
+// UNSUPPORTED: no-localization
+// UNSUPPORTED: libcpp-has-no-experimental-syncstream
+
+// <syncstream>
+
+// template <class charT, class traits, class Allocator>
+// class basic_osyncstream;
+
+// explicit basic_osyncstream(basic_ostream&  os);
+
+#include <cassert>
+#include <concepts>
+#include <syncstream>
+#include <sstream>
+
+#include "test_macros.h"
+#include "constexpr_char_traits.h"
+#include "test_allocator.h"
+
+template <class CharT>
+void test() {
+  {
+    using OS = std::basic_osyncstream<CharT>;
+    using W  = std::basic_ostringstream<CharT>;
+
+    static_assert(!std::convertible_to<std::basic_ostream<CharT>&, OS>);
+    static_assert(std::constructible_from<OS, std::basic_ostream<CharT>&>);
+
+    {
+      W w;
+#if defined(_LIBCPP_VERSION) && !defined(TEST_HAS_NO_THREADS)
+      assert(std::__wrapped_streambuf_mutex::__instance().__get_count(w.rdbuf()) == 0);
+#endif
+      {
+        OS os{w};
+#if defined(_LIBCPP_VERSION) && !defined(TEST_HAS_NO_THREADS)
+        assert(std::__wrapped_streambuf_mutex::__instance().__get_count(w.rdbuf()) == 1);
+#endif
+        assert(os.get_wrapped() == w.rdbuf());
+        assert(os.rdbuf()->get_wrapped() == w.rdbuf());
+        assert(os.rdbuf()->get_allocator() == std::allocator<CharT>());
+      }
+#if defined(_LIBCPP_VERSION) && !defined(TEST_HAS_NO_THREADS)
+      assert(std::__wrapped_streambuf_mutex::__instance().__get_count(w.rdbuf()) == 0);
+#endif
+    }
+  }
+
+  {
+    using OS = std::basic_osyncstream<CharT, constexpr_char_traits<CharT>>;
+    using W  = std::basic_ostringstream<CharT, constexpr_char_traits<CharT>>;
+
+    static_assert(!std::convertible_to<std::basic_ostream<CharT, constexpr_char_traits<CharT>>&, OS>);
+    static_assert(std::constructible_from<OS, std::basic_ostream<CharT, constexpr_char_traits<CharT>>&>);
+
+    {
+      W w;
+#if defined(_LIBCPP_VERSION) && !defined(TEST_HAS_NO_THREADS)
+      assert(std::__wrapped_streambuf_mutex::__instance().__get_count(w.rdbuf()) == 0);
+#endif
+      {
+        OS os{w};
+#if defined(_LIBCPP_VERSION) && !defined(TEST_HAS_NO_THREADS)
+        assert(std::__wrapped_streambuf_mutex::__instance().__get_count(w.rdbuf()) == 1);
+#endif
+        assert(os.get_wrapped() == w.rdbuf());
+        assert(os.rdbuf()->get_wrapped() == w.rdbuf());
+        assert(os.rdbuf()->get_allocator() == std::allocator<CharT>());
+      }
+#if defined(_LIBCPP_VERSION) && !defined(TEST_HAS_NO_THREADS)
+      assert(std::__wrapped_streambuf_mutex::__instance().__get_count(w.rdbuf()) == 0);
+#endif
+    }
+  }
+
+  {
+    using OS = std::basic_osyncstream<CharT, constexpr_char_traits<CharT>, test_allocator<CharT>>;
+    using W  = std::basic_ostringstream<CharT, constexpr_char_traits<CharT>, test_allocator<CharT>>;
+
+    static_assert(!std::convertible_to<std::basic_ostream<CharT, constexpr_char_traits<CharT>>&, OS>);
+    static_assert(std::constructible_from<OS, std::basic_ostream<CharT, constexpr_char_traits<CharT>>&>);
+
+    {
+      W w;
+#if defined(_LIBCPP_VERSION) && !defined(TEST_HAS_NO_THREADS)
+      assert(std::__wrapped_streambuf_mutex::__instance().__get_count(w.rdbuf()) == 0);
+#endif
+      {
+        OS os{w};
+#if defined(_LIBCPP_VERSION) && !defined(TEST_HAS_NO_THREADS)
+        assert(std::__wrapped_streambuf_mutex::__instance().__get_count(w.rdbuf()) == 1);
+#endif
+        assert(os.get_wrapped() == w.rdbuf());
+        assert(os.rdbuf()->get_wrapped() == w.rdbuf());
+        assert(os.rdbuf()->get_allocator() == test_allocator<CharT>());
+      }
+#if defined(_LIBCPP_VERSION) && !defined(TEST_HAS_NO_THREADS)
+      assert(std::__wrapped_streambuf_mutex::__instance().__get_count(w.rdbuf()) == 0);
+#endif
+    }
+  }
+}
+
+int main(int, char**) {
+  test<char>();
+#ifndef TEST_HAS_NO_WIDE_CHARACTERS
+  test<wchar_t>();
+#endif
+
+  return 0;
+}

diff  --git a/libcxx/test/std/input.output/syncstream/osyncstream/syncstream.osyncstream.cons/cons.pointer.allocator.pass.cpp b/libcxx/test/std/input.output/syncstream/osyncstream/syncstream.osyncstream.cons/cons.pointer.allocator.pass.cpp
new file mode 100644
index 000000000000000..eb3ac284cc68e7c
--- /dev/null
+++ b/libcxx/test/std/input.output/syncstream/osyncstream/syncstream.osyncstream.cons/cons.pointer.allocator.pass.cpp
@@ -0,0 +1,132 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17
+// UNSUPPORTED: no-localization
+// UNSUPPORTED: libcpp-has-no-experimental-syncstream
+
+// <syncstream>
+
+// template <class charT, class traits, class Allocator>
+// class basic_osyncstream;
+
+// basic_osyncstream(streambuf_type* os, const Allocator& allocator);
+
+#include <cassert>
+#include <concepts>
+#include <syncstream>
+#include <sstream>
+
+#include "test_macros.h"
+#include "constexpr_char_traits.h"
+#include "test_allocator.h"
+
+template <class CharT>
+void test() {
+  {
+    using OS = std::basic_osyncstream<CharT>;
+    using W  = std::basic_stringbuf<CharT>;
+
+    const std::allocator<CharT> alloc;
+    {
+      OS os = {nullptr, alloc};
+      assert(os.get_wrapped() == nullptr);
+      assert(os.rdbuf()->get_wrapped() == nullptr);
+      assert(os.rdbuf()->get_allocator() == alloc);
+    }
+    {
+      W w;
+#if defined(_LIBCPP_VERSION) && !defined(TEST_HAS_NO_THREADS)
+      assert(std::__wrapped_streambuf_mutex::__instance().__get_count(&w) == 0);
+#endif
+      {
+        OS os = {&w, alloc};
+#if defined(_LIBCPP_VERSION) && !defined(TEST_HAS_NO_THREADS)
+        assert(std::__wrapped_streambuf_mutex::__instance().__get_count(&w) == 1);
+#endif
+        assert(os.get_wrapped() == &w);
+        assert(os.rdbuf()->get_wrapped() == &w);
+        assert(os.rdbuf()->get_allocator() == alloc);
+      }
+#if defined(_LIBCPP_VERSION) && !defined(TEST_HAS_NO_THREADS)
+      assert(std::__wrapped_streambuf_mutex::__instance().__get_count(&w) == 0);
+#endif
+    }
+  }
+
+  {
+    using OS = std::basic_osyncstream<CharT, constexpr_char_traits<CharT>>;
+    using W  = std::basic_stringbuf<CharT, constexpr_char_traits<CharT>>;
+
+    const std::allocator<CharT> alloc;
+    {
+      OS os = {nullptr, alloc};
+      assert(os.get_wrapped() == nullptr);
+      assert(os.rdbuf()->get_wrapped() == nullptr);
+      assert(os.rdbuf()->get_allocator() == alloc);
+    }
+    {
+      W w;
+#if defined(_LIBCPP_VERSION) && !defined(TEST_HAS_NO_THREADS)
+      assert(std::__wrapped_streambuf_mutex::__instance().__get_count(&w) == 0);
+#endif
+      {
+        OS os = {&w, alloc};
+#if defined(_LIBCPP_VERSION) && !defined(TEST_HAS_NO_THREADS)
+        assert(std::__wrapped_streambuf_mutex::__instance().__get_count(&w) == 1);
+#endif
+        assert(os.get_wrapped() == &w);
+        assert(os.rdbuf()->get_wrapped() == &w);
+        assert(os.rdbuf()->get_allocator() == alloc);
+      }
+#if defined(_LIBCPP_VERSION) && !defined(TEST_HAS_NO_THREADS)
+      assert(std::__wrapped_streambuf_mutex::__instance().__get_count(&w) == 0);
+#endif
+    }
+  }
+
+  {
+    using OS = std::basic_osyncstream<CharT, constexpr_char_traits<CharT>, test_allocator<CharT>>;
+    using W  = std::basic_stringbuf<CharT, constexpr_char_traits<CharT>, test_allocator<CharT>>;
+
+    const test_allocator<CharT> alloc;
+    {
+      OS os = {nullptr, alloc};
+      assert(os.get_wrapped() == nullptr);
+      assert(os.rdbuf()->get_wrapped() == nullptr);
+      assert(os.rdbuf()->get_allocator() == alloc);
+    }
+    {
+      W w;
+#if defined(_LIBCPP_VERSION) && !defined(TEST_HAS_NO_THREADS)
+      assert(std::__wrapped_streambuf_mutex::__instance().__get_count(&w) == 0);
+#endif
+      {
+        OS os = {&w, alloc};
+#if defined(_LIBCPP_VERSION) && !defined(TEST_HAS_NO_THREADS)
+        assert(std::__wrapped_streambuf_mutex::__instance().__get_count(&w) == 1);
+#endif
+        assert(os.get_wrapped() == &w);
+        assert(os.rdbuf()->get_wrapped() == &w);
+        assert(os.rdbuf()->get_allocator() == alloc);
+      }
+#if defined(_LIBCPP_VERSION) && !defined(TEST_HAS_NO_THREADS)
+      assert(std::__wrapped_streambuf_mutex::__instance().__get_count(&w) == 0);
+#endif
+    }
+  }
+}
+
+int main(int, char**) {
+  test<char>();
+#ifndef TEST_HAS_NO_WIDE_CHARACTERS
+  test<wchar_t>();
+#endif
+
+  return 0;
+}

diff  --git a/libcxx/test/std/input.output/syncstream/osyncstream/syncstream.osyncstream.cons/cons.pointer.pass.cpp b/libcxx/test/std/input.output/syncstream/osyncstream/syncstream.osyncstream.cons/cons.pointer.pass.cpp
new file mode 100644
index 000000000000000..c98a8894dd004a2
--- /dev/null
+++ b/libcxx/test/std/input.output/syncstream/osyncstream/syncstream.osyncstream.cons/cons.pointer.pass.cpp
@@ -0,0 +1,140 @@
+//==----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17
+// UNSUPPORTED: no-localization
+// UNSUPPORTED: libcpp-has-no-experimental-syncstream
+
+// <syncstream>
+
+// template <class charT, class traits, class Allocator>
+// class basic_osyncstream;
+
+// explicit basic_osyncstream(streambuf_type* os);
+
+#include <cassert>
+#include <concepts>
+#include <syncstream>
+#include <sstream>
+
+#include "test_macros.h"
+#include "constexpr_char_traits.h"
+#include "test_allocator.h"
+
+template <class CharT>
+void test() {
+  {
+    using OS = std::basic_osyncstream<CharT>;
+    using W  = std::basic_stringbuf<CharT>;
+
+    static_assert(!std::convertible_to<std::basic_syncbuf<CharT>*, OS>);
+    static_assert(std::constructible_from<OS, std::basic_syncbuf<CharT>*>);
+
+    {
+      OS os{nullptr};
+      assert(os.get_wrapped() == nullptr);
+      assert(os.rdbuf()->get_wrapped() == nullptr);
+      assert(os.rdbuf()->get_allocator() == std::allocator<CharT>());
+    }
+    {
+      W w;
+#if defined(_LIBCPP_VERSION) && !defined(TEST_HAS_NO_THREADS)
+      assert(std::__wrapped_streambuf_mutex::__instance().__get_count(&w) == 0);
+#endif
+      {
+        OS os{&w};
+#if defined(_LIBCPP_VERSION) && !defined(TEST_HAS_NO_THREADS)
+        assert(std::__wrapped_streambuf_mutex::__instance().__get_count(&w) == 1);
+#endif
+        assert(os.get_wrapped() == &w);
+        assert(os.rdbuf()->get_wrapped() == &w);
+        assert(os.rdbuf()->get_allocator() == std::allocator<CharT>());
+      }
+#if defined(_LIBCPP_VERSION) && !defined(TEST_HAS_NO_THREADS)
+      assert(std::__wrapped_streambuf_mutex::__instance().__get_count(&w) == 0);
+#endif
+    }
+  }
+
+  {
+    using OS = std::basic_osyncstream<CharT, constexpr_char_traits<CharT>>;
+    using W  = std::basic_stringbuf<CharT, constexpr_char_traits<CharT>>;
+
+    static_assert(!std::convertible_to<std::basic_syncbuf<CharT, constexpr_char_traits<CharT>>*, OS>);
+    static_assert(std::constructible_from<OS, std::basic_syncbuf<CharT, constexpr_char_traits<CharT>>*>);
+
+    {
+      OS os{nullptr};
+      assert(os.get_wrapped() == nullptr);
+      assert(os.rdbuf()->get_wrapped() == nullptr);
+      assert(os.rdbuf()->get_allocator() == std::allocator<CharT>());
+    }
+    {
+      W w;
+#if defined(_LIBCPP_VERSION) && !defined(TEST_HAS_NO_THREADS)
+      assert(std::__wrapped_streambuf_mutex::__instance().__get_count(&w) == 0);
+#endif
+      {
+        OS os{&w};
+#if defined(_LIBCPP_VERSION) && !defined(TEST_HAS_NO_THREADS)
+        assert(std::__wrapped_streambuf_mutex::__instance().__get_count(&w) == 1);
+#endif
+        assert(os.get_wrapped() == &w);
+        assert(os.rdbuf()->get_wrapped() == &w);
+        assert(os.rdbuf()->get_allocator() == std::allocator<CharT>());
+      }
+#if defined(_LIBCPP_VERSION) && !defined(TEST_HAS_NO_THREADS)
+      assert(std::__wrapped_streambuf_mutex::__instance().__get_count(&w) == 0);
+#endif
+    }
+  }
+
+  {
+    using OS = std::basic_osyncstream<CharT, constexpr_char_traits<CharT>, test_allocator<CharT>>;
+    using W  = std::basic_stringbuf<CharT, constexpr_char_traits<CharT>, test_allocator<CharT>>;
+
+    static_assert(
+        !std::convertible_to<std::basic_syncbuf<CharT, constexpr_char_traits<CharT>, test_allocator<CharT>>*, OS>);
+    static_assert(
+        std::constructible_from<OS, std::basic_syncbuf<CharT, constexpr_char_traits<CharT>, test_allocator<CharT>>*>);
+
+    {
+      OS os{nullptr};
+      assert(os.get_wrapped() == nullptr);
+      assert(os.rdbuf()->get_wrapped() == nullptr);
+      assert(os.rdbuf()->get_allocator() == test_allocator<CharT>());
+    }
+    {
+      W w;
+#if defined(_LIBCPP_VERSION) && !defined(TEST_HAS_NO_THREADS)
+      assert(std::__wrapped_streambuf_mutex::__instance().__get_count(&w) == 0);
+#endif
+      {
+        OS os{&w};
+#if defined(_LIBCPP_VERSION) && !defined(TEST_HAS_NO_THREADS)
+        assert(std::__wrapped_streambuf_mutex::__instance().__get_count(&w) == 1);
+#endif
+        assert(os.get_wrapped() == &w);
+        assert(os.rdbuf()->get_wrapped() == &w);
+        assert(os.rdbuf()->get_allocator() == test_allocator<CharT>());
+      }
+#if defined(_LIBCPP_VERSION) && !defined(TEST_HAS_NO_THREADS)
+      assert(std::__wrapped_streambuf_mutex::__instance().__get_count(&w) == 0);
+#endif
+    }
+  }
+}
+
+int main(int, char**) {
+  test<char>();
+#ifndef TEST_HAS_NO_WIDE_CHARACTERS
+  test<wchar_t>();
+#endif
+
+  return 0;
+}

diff  --git a/libcxx/test/std/input.output/syncstream/osyncstream/thread/basic.pass.cpp b/libcxx/test/std/input.output/syncstream/osyncstream/thread/basic.pass.cpp
new file mode 100644
index 000000000000000..4d7a54dbaa51806
--- /dev/null
+++ b/libcxx/test/std/input.output/syncstream/osyncstream/thread/basic.pass.cpp
@@ -0,0 +1,60 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17
+// UNSUPPORTED: no-localization
+// UNSUPPORTED: no-threads
+// UNSUPPORTED: libcpp-has-no-experimental-syncstream
+
+// <syncstream>
+
+// template <class charT, class traits, class Allocator>
+// class basic_osyncstream;
+
+// Basic test whether the code works in a threaded environment.
+// Using timing the output order should be stable.
+// several_threads.pass.cpp tests with more threads.
+
+#include <syncstream>
+#include <sstream>
+#include <mutex>
+#include <thread>
+#include <cassert>
+#include <iostream>
+
+#include "test_macros.h"
+
+static std::basic_ostringstream<char> ss;
+static const char a = 'a';
+static const char b = 'b';
+static const char c = 'c';
+static const char d = 'd';
+
+void f1() {
+  std::basic_osyncstream<char> out(ss);
+  out << a;
+  std::this_thread::sleep_for(std::chrono::milliseconds(250));
+  out << b;
+}
+
+void f2() {
+  std::basic_osyncstream<char> out(ss);
+  out << c;
+  out << d;
+}
+
+int main(int, char**) {
+  std::thread t1(f1);
+  std::thread t2(f2);
+  t1.join();
+  t2.join();
+
+  assert(ss.str() == "cdab");
+
+  return 0;
+}

diff  --git a/libcxx/test/std/input.output/syncstream/osyncstream/thread/several_threads.pass.cpp b/libcxx/test/std/input.output/syncstream/osyncstream/thread/several_threads.pass.cpp
new file mode 100644
index 000000000000000..dbf2ded1b532e2b
--- /dev/null
+++ b/libcxx/test/std/input.output/syncstream/osyncstream/thread/several_threads.pass.cpp
@@ -0,0 +1,144 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17
+// UNSUPPORTED: no-localization
+// UNSUPPORTED: no-threads
+// UNSUPPORTED: libcpp-has-no-experimental-syncstream
+
+// <syncstream>
+
+// template <class charT, class traits, class Allocator>
+// class basic_osyncstream;
+
+// The test writes all elements in test_strings in a random order in ss. Every
+// write is done by an osyncstream. This means the output is in random order,
+// but the words should be written without interleaving. To increment the
+// change of interleaving words are written one character at a time.
+
+#include <cassert>
+#include <chrono>
+#include <mutex>
+#include <sstream>
+#include <string>
+#include <syncstream>
+#include <thread>
+#include <unordered_set>
+#include <vector>
+
+#include "test_macros.h"
+
+static std::ostringstream ss;
+
+static std::unordered_multiset<std::string> test_strings = {
+    "C++ ",
+    "is ",
+    "a ",
+    "general-purpose ",
+    "programming ",
+    "language ",
+    "created ",
+    "by ",
+    "Bjarne ",
+    "Stroustrup ",
+    "as ",
+    "an ",
+    "extension ",
+    "of ",
+    "the ",
+    "C ",
+    "programming ",
+    "language, ",
+    "or ",
+    "C ",
+    "with ",
+    "Classes. ",
+    "The ",
+    "language ",
+    "has ",
+    "expanded ",
+    "significantly ",
+    "over ",
+    "time, ",
+    "and ",
+    "modern ",
+    "C++ ",
+    "has ",
+    "object-oriented, ",
+    "generic, ",
+    "and ",
+    "functional ",
+    "features ",
+    "in ",
+    "addition ",
+    "to ",
+    "facilities ",
+    "for ",
+    "low-level ",
+    "memory ",
+    "manipulation. ",
+    "It ",
+    "is ",
+    "almost ",
+    "always ",
+    "implemented ",
+    "as ",
+    "a ",
+    "compiled ",
+    "language, ",
+    "and ",
+    "many ",
+    "vendors ",
+    "provide ",
+    "C++ ",
+    "compilers, ",
+    "including ",
+    "the ",
+    "Free ",
+    "Software ",
+    "Foundation, ",
+    "LLVM, ",
+    "Microsoft, ",
+    "Intel, ",
+    "and ",
+    "IBM, ",
+    "so ",
+    "it ",
+    "is ",
+    "available ",
+    "on ",
+    "many ",
+    "platforms."};
+
+void f(std::string text) {
+  std::osyncstream out(ss);
+  for (char c : text)
+    out << c;
+}
+
+void test() {
+  ss = std::basic_ostringstream<char>();
+  std::vector<std::thread> threads;
+  for (std::string const& word : test_strings)
+    threads.push_back(std::thread(f, word));
+
+  for (auto& thread : threads)
+    thread.join();
+
+  std::string output = ss.str();
+  for (const std::string& word : test_strings)
+    assert(output.find(word) != std::string::npos);
+}
+
+int main(int, char**) {
+  // The more we test, the more likely we catch an error
+  for (size_t i = 0; i < 1024; ++i)
+    test();
+
+  return 0;
+}

diff  --git a/libcxx/test/std/input.output/syncstream/osyncstream/types.compile.pass.cpp b/libcxx/test/std/input.output/syncstream/osyncstream/types.compile.pass.cpp
new file mode 100644
index 000000000000000..f222ad82f3650e4
--- /dev/null
+++ b/libcxx/test/std/input.output/syncstream/osyncstream/types.compile.pass.cpp
@@ -0,0 +1,92 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17
+// UNSUPPORTED: no-localization
+// UNSUPPORTED: libcpp-has-no-experimental-syncstream
+
+// <syncstream>
+//
+//  template<class charT, class traits = char_traits<charT>, class Allocator = allocator<charT>>
+//  class basic_osyncstream : public basic_ostream<charT, traits> {
+//  public:
+//    using char_type   = charT;
+//    using int_type    = typename traits::int_type;
+//    using pos_type    = typename traits::pos_type;
+//    using off_type    = typename traits::off_type;
+//    using traits_type = traits;
+//
+//    using allocator_type = Allocator;
+//    using streambuf_type = basic_streambuf<charT, traits>;
+//    using syncbuf_type   = basic_syncbuf<charT, traits, Allocator>;
+
+#include <syncstream>
+#include <concepts>
+
+#include "test_macros.h"
+#include "constexpr_char_traits.h"
+#include "test_allocator.h"
+
+static_assert(std::same_as<std::basic_osyncstream<char>,
+                           std::basic_osyncstream<char, std::char_traits<char>, std::allocator<char>>>);
+static_assert(std::same_as<std::basic_osyncstream<char, constexpr_char_traits<char>>,
+                           std::basic_osyncstream<char, constexpr_char_traits<char>, std::allocator<char>>>);
+
+static_assert(
+    std::same_as<std::basic_osyncstream<char, constexpr_char_traits<char>, test_allocator<char>>::char_type, char>);
+static_assert(std::same_as<std::basic_osyncstream<char, constexpr_char_traits<char>, test_allocator<char>>::int_type,
+                           constexpr_char_traits<char>::int_type>);
+static_assert(std::same_as<std::basic_osyncstream<char, constexpr_char_traits<char>, test_allocator<char>>::pos_type,
+                           constexpr_char_traits<char>::pos_type>);
+static_assert(std::same_as<std::basic_osyncstream<char, constexpr_char_traits<char>, test_allocator<char>>::off_type,
+                           constexpr_char_traits<char>::off_type>);
+static_assert(std::same_as<std::basic_osyncstream<char, constexpr_char_traits<char>, test_allocator<char>>::traits_type,
+                           constexpr_char_traits<char>>);
+static_assert(
+    std::same_as<std::basic_osyncstream<char, constexpr_char_traits<char>, test_allocator<char>>::allocator_type,
+                 test_allocator<char>>);
+static_assert(
+    std::same_as<std::basic_osyncstream<char, constexpr_char_traits<char>, test_allocator<char>>::streambuf_type,
+                 std::basic_streambuf<char, constexpr_char_traits<char>>>);
+static_assert(
+    std::same_as<std::basic_osyncstream<char, constexpr_char_traits<char>, test_allocator<char>>::syncbuf_type,
+                 std::basic_syncbuf<char, constexpr_char_traits<char>, test_allocator<char>>>);
+
+#ifndef TEST_HAS_NO_WIDE_CHARACTERS
+
+static_assert(std::same_as<std::basic_osyncstream<wchar_t>,
+                           std::basic_osyncstream<wchar_t, std::char_traits<wchar_t>, std::allocator<wchar_t>>>);
+static_assert(std::same_as<std::basic_osyncstream<wchar_t, constexpr_char_traits<wchar_t>>,
+                           std::basic_osyncstream<wchar_t, constexpr_char_traits<wchar_t>, std::allocator<wchar_t>>>);
+
+static_assert(
+    std::same_as<std::basic_osyncstream<wchar_t, constexpr_char_traits<wchar_t>, test_allocator<wchar_t>>::char_type,
+                 wchar_t>);
+static_assert(
+    std::same_as<std::basic_osyncstream<wchar_t, constexpr_char_traits<wchar_t>, test_allocator<wchar_t>>::int_type,
+                 constexpr_char_traits<wchar_t>::int_type>);
+static_assert(
+    std::same_as<std::basic_osyncstream<wchar_t, constexpr_char_traits<wchar_t>, test_allocator<wchar_t>>::pos_type,
+                 constexpr_char_traits<wchar_t>::pos_type>);
+static_assert(
+    std::same_as<std::basic_osyncstream<wchar_t, constexpr_char_traits<wchar_t>, test_allocator<wchar_t>>::off_type,
+                 constexpr_char_traits<wchar_t>::off_type>);
+static_assert(
+    std::same_as<std::basic_osyncstream<wchar_t, constexpr_char_traits<wchar_t>, test_allocator<wchar_t>>::traits_type,
+                 constexpr_char_traits<wchar_t>>);
+static_assert(std::same_as<
+              std::basic_osyncstream<wchar_t, constexpr_char_traits<wchar_t>, test_allocator<wchar_t>>::allocator_type,
+              test_allocator<wchar_t>>);
+static_assert(std::same_as<
+              std::basic_osyncstream<wchar_t, constexpr_char_traits<wchar_t>, test_allocator<wchar_t>>::streambuf_type,
+              std::basic_streambuf<wchar_t, constexpr_char_traits<wchar_t>>>);
+static_assert(
+    std::same_as<std::basic_osyncstream<wchar_t, constexpr_char_traits<wchar_t>, test_allocator<wchar_t>>::syncbuf_type,
+                 std::basic_syncbuf<wchar_t, constexpr_char_traits<wchar_t>, test_allocator<wchar_t>>>);
+
+#endif // TEST_HAS_NO_WIDE_CHARACTERS

diff  --git a/libcxx/test/std/input.output/syncstream/syncbuf/helpers.h b/libcxx/test/std/input.output/syncstream/syncbuf/helpers.h
new file mode 100644
index 000000000000000..79fcaafa80956d2
--- /dev/null
+++ b/libcxx/test/std/input.output/syncstream/syncbuf/helpers.h
@@ -0,0 +1,38 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef TEST_STD_INPUT_OUTPUT_SYNCSTREAM_SYNCBUF_SYNCSTREAM_SYNCBUF_MEMBERS_H
+#define TEST_STD_INPUT_OUTPUT_SYNCSTREAM_SYNCBUF_SYNCSTREAM_SYNCBUF_MEMBERS_H
+
+#include <syncstream>
+
+template <class T>
+class test_buf : public std::basic_streambuf<T> {
+public:
+  int id;
+
+  test_buf(int _id = 0) : id(_id) {}
+
+  T* _pptr() { return this->pptr(); }
+};
+
+template <class T, class Alloc = std::allocator<T>>
+class test_syncbuf : public std::basic_syncbuf<T, std::char_traits<T>, Alloc> {
+public:
+  test_syncbuf(test_buf<T>* buf, Alloc alloc) : std::basic_syncbuf<T, std::char_traits<T>, Alloc>(buf, alloc) {}
+
+  void _setp(T* begin, T* end) { return this->setp(begin, end); }
+};
+
+template <class T>
+struct test_allocator : std::allocator<T> {
+  int id;
+  test_allocator(int _id = 0) : id(_id) {}
+};
+
+#endif // TEST_STD_INPUT_OUTPUT_SYNCSTREAM_SYNCBUF_SYNCSTREAM_SYNCBUF_MEMBERS_H

diff  --git a/libcxx/test/std/input.output/syncstream/syncbuf/sputc.pass.cpp b/libcxx/test/std/input.output/syncstream/syncbuf/sputc.pass.cpp
new file mode 100644
index 000000000000000..6812e0abb5924fc
--- /dev/null
+++ b/libcxx/test/std/input.output/syncstream/syncbuf/sputc.pass.cpp
@@ -0,0 +1,151 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17
+// UNSUPPORTED: no-localization
+// UNSUPPORTED: libcpp-has-no-experimental-syncstream
+
+// <syncstream>
+
+// template <class charT, class traits, class Allocator>
+// class basic_syncbuf;
+
+// Tests the inherited function using a custom allocator.
+//
+// int_type basic_streambuf<charT, traits>::sputc(char_type c);
+//
+// This test also validates the observable behaviour after move assignment and
+// construction. This test uses a small so the underlying string is in short
+// mode.
+
+#include <array>
+#include <syncstream>
+#include <cassert>
+#include <sstream>
+
+#include "test_macros.h"
+#include "test_allocator.h"
+
+template <class CharT>
+void test() {
+  std::array< CharT, 17> input{
+      CharT('a'),
+      CharT('1'),
+      CharT('+'),
+      CharT('A'),
+      CharT('g'),
+      CharT('0'),
+      CharT('@'),
+      CharT('Z'),
+      CharT('q'),
+      CharT('8'),
+      CharT('#'),
+      CharT('D'),
+      CharT('t'),
+      CharT('9'),
+      CharT('$'),
+      CharT('A'),
+      CharT(' ')};
+  using SyncBuf = std::basic_syncbuf<CharT, std::char_traits<CharT>, test_allocator<CharT>>;
+
+  { // Normal
+    std::basic_string<CharT> expected;
+    std::basic_stringbuf<CharT> buf;
+    test_allocator_statistics stats;
+    test_allocator<CharT> allocator{&stats};
+
+    {
+      SyncBuf sync_buf{&buf, allocator};
+      for (int i = 0; i < 1024; ++i) {
+        CharT c = input[i % input.size()];
+        expected.push_back(c);
+        typename SyncBuf::int_type ret = sync_buf.sputc(c);
+        assert(ret == typename SyncBuf::int_type(c));
+      }
+      // The synchchronization happens upon destruction of sync_buf.
+      assert(buf.str().empty());
+      assert(stats.allocated_size >= 1024);
+    }
+    assert(buf.str() == expected);
+    assert(stats.allocated_size == 0);
+  }
+  { // Move construction
+    std::basic_stringbuf<CharT> buf;
+    test_allocator_statistics stats;
+    test_allocator<CharT> allocator{&stats};
+
+    {
+      SyncBuf sync_buf{&buf, allocator};
+      CharT c                        = CharT('4');
+      typename SyncBuf::int_type ret = sync_buf.sputc(c);
+      assert(ret == typename SyncBuf::int_type(c));
+
+      {
+        c = CharT('2');
+
+        SyncBuf new_sync_buf{std::move(sync_buf)};
+        ret = new_sync_buf.sputc(c);
+        assert(ret == typename SyncBuf::int_type(c));
+
+        // The synchchronization happens upon destruction of new_sync_buf.
+        assert(buf.str().empty());
+        assert(stats.allocated_size >= 2);
+      }
+      assert(buf.str().size() == 2);
+      assert(buf.str()[0] == CharT('4'));
+      assert(buf.str()[1] == CharT('2'));
+      assert(stats.allocated_size == 0);
+    }
+    assert(buf.str().size() == 2);
+    assert(buf.str()[0] == CharT('4'));
+    assert(buf.str()[1] == CharT('2'));
+    assert(stats.allocated_size == 0);
+  }
+  { // Move assignment non-propagating allocator
+    std::basic_stringbuf<CharT> buf;
+    test_allocator_statistics stats;
+    test_allocator<CharT> allocator{&stats};
+    static_assert(!std::allocator_traits<test_allocator<CharT>>::propagate_on_container_move_assignment::value);
+
+    {
+      SyncBuf sync_buf{&buf, allocator};
+      CharT c                        = CharT('4');
+      typename SyncBuf::int_type ret = sync_buf.sputc(c);
+      assert(ret == typename SyncBuf::int_type(c));
+
+      {
+        c = CharT('2');
+
+        SyncBuf new_sync_buf;
+        test_allocator<CharT> a = new_sync_buf.get_allocator();
+        new_sync_buf            = std::move(sync_buf);
+        assert(new_sync_buf.get_allocator() == a);
+
+        ret = new_sync_buf.sputc(c);
+        assert(ret == typename SyncBuf::int_type(c));
+
+        // The synchchronization happens upon destruction of new_sync_buf.
+        assert(buf.str().empty());
+      }
+      assert(buf.str().size() == 2);
+      assert(buf.str()[0] == CharT('4'));
+      assert(buf.str()[1] == CharT('2'));
+    }
+    assert(buf.str().size() == 2);
+    assert(buf.str()[0] == CharT('4'));
+    assert(buf.str()[1] == CharT('2'));
+  }
+}
+
+int main(int, char**) {
+  test<char>();
+#ifndef TEST_HAS_NO_WIDE_CHARACTERS
+  test<wchar_t>();
+#endif
+  return 0;
+}

diff  --git a/libcxx/test/std/input.output/syncstream/syncbuf/sputn.pass.cpp b/libcxx/test/std/input.output/syncstream/syncbuf/sputn.pass.cpp
new file mode 100644
index 000000000000000..d706010843d62af
--- /dev/null
+++ b/libcxx/test/std/input.output/syncstream/syncbuf/sputn.pass.cpp
@@ -0,0 +1,134 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17
+// UNSUPPORTED: no-localization
+// UNSUPPORTED: libcpp-has-no-experimental-syncstream
+
+// <syncstream>
+
+// template <class charT, class traits, class Allocator>
+// class basic_syncbuf;
+
+// Tests the inherited function using a custom allocator.
+//
+// streamsize basic_streambuf<charT, traits>::sputc(const char_type* s, streamsize n);
+//
+// This test also validates the observable behaviour after move assignment and
+// construction. This test uses a large buffer so the underlying string is in
+// long mode.
+
+#include <array>
+#include <syncstream>
+#include <cassert>
+#include <sstream>
+
+#include "test_macros.h"
+#include "test_allocator.h"
+
+template <class CharT>
+void test() {
+  std::array< CharT, 17> input{
+      CharT('a'),
+      CharT('1'),
+      CharT('+'),
+      CharT('A'),
+      CharT('g'),
+      CharT('0'),
+      CharT('@'),
+      CharT('Z'),
+      CharT('q'),
+      CharT('8'),
+      CharT('#'),
+      CharT('D'),
+      CharT('t'),
+      CharT('9'),
+      CharT('$'),
+      CharT('A'),
+      CharT(' ')};
+  std::basic_string<CharT> expected;
+  for (int i = 0; i < 1024; ++i)
+    expected.push_back(input[i % input.size()]);
+
+  using SyncBuf = std::basic_syncbuf<CharT, std::char_traits<CharT>, test_allocator<CharT>>;
+  { // Normal
+    std::basic_stringbuf<CharT> buf;
+    test_allocator_statistics stats;
+    test_allocator<CharT> allocator{&stats};
+
+    {
+      SyncBuf sync_buf{&buf, allocator};
+      std::streamsize ret = sync_buf.sputn(expected.data(), expected.size());
+      assert(ret == 1024);
+
+      // The synchchronization happens upon destruction of sync_buf.
+      assert(buf.str().empty());
+      assert(stats.allocated_size >= 1024);
+    }
+    assert(buf.str() == expected);
+    assert(stats.allocated_size == 0);
+  }
+  { // Move construction
+    std::basic_stringbuf<CharT> buf;
+    test_allocator_statistics stats;
+    test_allocator<CharT> allocator{&stats};
+
+    {
+      SyncBuf sync_buf{&buf, allocator};
+      std::streamsize ret = sync_buf.sputn(expected.data(), expected.size());
+      assert(ret == 1024);
+      {
+        SyncBuf new_sync_buf{std::move(sync_buf)};
+        ret = new_sync_buf.sputn(expected.data(), expected.size());
+        assert(ret == 1024);
+
+        // The synchchronization happens upon destruction of new_sync_buf.
+        assert(buf.str().empty());
+        assert(stats.allocated_size >= 2048);
+      }
+      assert(buf.str() == expected + expected);
+      assert(stats.allocated_size == 0);
+    }
+    assert(buf.str() == expected + expected);
+    assert(stats.allocated_size == 0);
+  }
+  { // Move assignment non-propagating allocator
+    std::basic_stringbuf<CharT> buf;
+    test_allocator_statistics stats;
+    test_allocator<CharT> allocator{&stats};
+    static_assert(!std::allocator_traits<test_allocator<CharT>>::propagate_on_container_move_assignment::value);
+
+    {
+      SyncBuf sync_buf{&buf, allocator};
+      std::streamsize ret = sync_buf.sputn(expected.data(), expected.size());
+      assert(ret == 1024);
+      {
+        SyncBuf new_sync_buf;
+        test_allocator<CharT> a = new_sync_buf.get_allocator();
+        new_sync_buf            = std::move(sync_buf);
+        assert(new_sync_buf.get_allocator() == a);
+
+        ret = new_sync_buf.sputn(expected.data(), expected.size());
+        assert(ret == 1024);
+
+        // The synchchronization happens upon destruction of new_sync_buf.
+        assert(buf.str().empty());
+      }
+      assert(buf.str() == expected + expected);
+    }
+    assert(buf.str() == expected + expected);
+  }
+}
+
+int main(int, char**) {
+  test<char>();
+#ifndef TEST_HAS_NO_WIDE_CHARACTERS
+  test<wchar_t>();
+#endif
+  return 0;
+}

diff  --git a/libcxx/test/std/input.output/syncstream/syncbuf/sync.pass.cpp b/libcxx/test/std/input.output/syncstream/syncbuf/sync.pass.cpp
new file mode 100644
index 000000000000000..26e48ff9096aed9
--- /dev/null
+++ b/libcxx/test/std/input.output/syncstream/syncbuf/sync.pass.cpp
@@ -0,0 +1,56 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17
+// UNSUPPORTED: no-localization
+// UNSUPPORTED: libcpp-has-no-experimental-syncstream
+
+// <syncstream>
+
+// template <class charT, class traits, class Allocator>
+// class basic_syncbuf;
+
+// protected:
+// [syncstream.syncbuf.virtuals], overridden virtual functions
+// int sync() override;
+
+#include <syncstream>
+#include <sstream>
+#include <ostream>
+#include <cassert>
+
+#include "test_macros.h"
+
+template <class CharT>
+void test_sync(bool emit_on_sync) {
+  std::basic_stringbuf<CharT> base;
+  std::basic_syncbuf<CharT> buff(&base);
+  std::basic_ostream<CharT> out(&buff);
+
+  buff.set_emit_on_sync(emit_on_sync);
+
+  out << 'a';
+  out.flush(); // This is an indirect call to sync.
+
+  if (emit_on_sync) {
+    assert(base.str().size() == 1);
+    assert(base.str()[0] == CharT('a'));
+  } else
+    assert(base.str().empty());
+}
+
+int main(int, char**) {
+  test_sync<char>(true);
+  test_sync<char>(false);
+#ifndef TEST_HAS_NO_WIDE_CHARACTERS
+  test_sync<wchar_t>(true);
+  test_sync<wchar_t>(false);
+#endif
+
+  return 0;
+}

diff  --git a/libcxx/test/std/input.output/syncstream/syncbuf/syncstream.syncbuf.assign/assign.pass.cpp b/libcxx/test/std/input.output/syncstream/syncbuf/syncstream.syncbuf.assign/assign.pass.cpp
new file mode 100644
index 000000000000000..f2309075717eb12
--- /dev/null
+++ b/libcxx/test/std/input.output/syncstream/syncbuf/syncstream.syncbuf.assign/assign.pass.cpp
@@ -0,0 +1,399 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17
+// UNSUPPORTED: no-localization
+// UNSUPPORTED: libcpp-has-no-experimental-syncstream
+
+// <syncstream>
+
+// template <class charT, class traits, class Allocator>
+// class basic_syncbuf;
+
+// basic_syncbuf& operator=(basic_syncbuf&& rhs);
+
+#include <syncstream>
+#include <sstream>
+#include <cassert>
+#include <concepts>
+
+#include "test_macros.h"
+
+template <class T, class propagate>
+struct test_allocator : std::allocator<T> {
+  using propagate_on_container_move_assignment = propagate;
+
+  int id{-1};
+
+  test_allocator(int _id = -1) : id(_id) {}
+  test_allocator(test_allocator const& other)            = default;
+  test_allocator(test_allocator&& other)                 = default;
+  test_allocator& operator=(const test_allocator& other) = default;
+
+  test_allocator& operator=(test_allocator&& other) {
+    if constexpr (propagate_on_container_move_assignment::value)
+      id = other.id;
+    else
+      id = -1;
+    return *this;
+  }
+};
+
+template <class T>
+class test_buf : public std::basic_streambuf<T> {
+public:
+  int id;
+
+  test_buf(int _id = 0) : id(_id) {}
+
+  T* _pptr() { return this->pptr(); }
+};
+
+template <class T, class Alloc = std::allocator<T>>
+class test_syncbuf : public std::basic_syncbuf<T, std::char_traits<T>, Alloc> {
+  using Base = std::basic_syncbuf<T, std::char_traits<T>, Alloc>;
+
+public:
+  test_syncbuf() = default;
+
+  test_syncbuf(test_buf<T>* buf, Alloc alloc) : Base(buf, alloc) {}
+
+  test_syncbuf(typename Base::streambuf_type* buf, Alloc alloc) : Base(buf, alloc) {}
+
+  void _setp(T* begin, T* end) { return this->setp(begin, end); }
+};
+
+// Helper wrapper to inspect the internal state of the basic_syncbuf
+//
+// This is used the valiate some standard requirements and libc++
+// implementation details.
+template <class CharT, class Traits, class Allocator>
+class syncbuf_inspector : public std::basic_syncbuf<CharT, Traits, Allocator> {
+public:
+  syncbuf_inspector() = default;
+  explicit syncbuf_inspector(std::basic_syncbuf<CharT, Traits, Allocator>&& base)
+      : std::basic_syncbuf<CharT, Traits, Allocator>(std::move(base)) {}
+
+  void operator=(std::basic_syncbuf<CharT, Traits, Allocator>&& base) { *this = std::move(base); }
+
+  using std::basic_syncbuf<CharT, Traits, Allocator>::pbase;
+  using std::basic_syncbuf<CharT, Traits, Allocator>::pptr;
+  using std::basic_syncbuf<CharT, Traits, Allocator>::epptr;
+};
+
+template <class CharT>
+static void test_assign() {
+  test_buf<CharT> base;
+
+  { // Test using the real class, propagating allocator.
+    using BuffT = std::basic_syncbuf<CharT, std::char_traits<CharT>, test_allocator<CharT, std::true_type>>;
+
+    BuffT buff1(&base, test_allocator<CharT, std::true_type>{42});
+    buff1.sputc(CharT('A'));
+
+    assert(buff1.get_wrapped() != nullptr);
+
+    BuffT buff2;
+    assert(buff2.get_allocator().id == -1);
+    buff2 = std::move(buff1);
+    assert(buff1.get_wrapped() == nullptr);
+    assert(buff2.get_wrapped() == &base);
+
+    assert(buff2.get_wrapped() == &base);
+    assert(buff2.get_allocator().id == 42);
+  }
+
+  { // Test using the real class, non-propagating allocator.
+    using BuffT = std::basic_syncbuf<CharT, std::char_traits<CharT>, test_allocator<CharT, std::false_type>>;
+
+    BuffT buff1(&base, test_allocator<CharT, std::false_type>{42});
+    buff1.sputc(CharT('A'));
+
+    assert(buff1.get_wrapped() != nullptr);
+
+    BuffT buff2;
+    assert(buff2.get_allocator().id == -1);
+    buff2 = std::move(buff1);
+    assert(buff1.get_wrapped() == nullptr);
+    assert(buff2.get_wrapped() == &base);
+
+    assert(buff2.get_wrapped() == &base);
+    assert(buff2.get_allocator().id == -1);
+  }
+
+  { // Move assignment propagating allocator
+    // Test using the inspection wrapper.
+    // Not all these requirements are explicitly in the Standard,
+    // however the asserts are based on secondary requirements. The
+    // LIBCPP_ASSERTs are implementation specific.
+
+    using BuffT = std::basic_syncbuf<CharT, std::char_traits<CharT>, std::allocator<CharT>>;
+
+    using Inspector = syncbuf_inspector<CharT, std::char_traits<CharT>, std::allocator<CharT>>;
+    Inspector inspector1{BuffT(&base)};
+    inspector1.sputc(CharT('A'));
+
+    assert(inspector1.get_wrapped() != nullptr);
+    assert(inspector1.pbase() != nullptr);
+    assert(inspector1.pptr() != nullptr);
+    assert(inspector1.epptr() != nullptr);
+    assert(inspector1.pbase() != inspector1.pptr());
+    assert(inspector1.pptr() - inspector1.pbase() == 1);
+    [[maybe_unused]] std::streamsize size = inspector1.epptr() - inspector1.pbase();
+
+    Inspector inspector2;
+    inspector2 = std::move(inspector1);
+
+    assert(inspector1.get_wrapped() == nullptr);
+    LIBCPP_ASSERT(inspector1.pbase() == nullptr);
+    LIBCPP_ASSERT(inspector1.pptr() == nullptr);
+    LIBCPP_ASSERT(inspector1.epptr() == nullptr);
+    assert(inspector1.pbase() == inspector1.pptr());
+
+    assert(inspector2.get_wrapped() == &base);
+    LIBCPP_ASSERT(inspector2.pbase() != nullptr);
+    LIBCPP_ASSERT(inspector2.pptr() != nullptr);
+    LIBCPP_ASSERT(inspector2.epptr() != nullptr);
+    assert(inspector2.pptr() - inspector2.pbase() == 1);
+    LIBCPP_ASSERT(inspector2.epptr() - inspector2.pbase() == size);
+  }
+}
+
+template <class CharT>
+static void test_basic() {
+  { // Test properties
+    std::basic_syncbuf<CharT> sync_buf1(nullptr);
+    std::basic_syncbuf<CharT> sync_buf2(nullptr);
+    [[maybe_unused]] std::same_as<std::basic_syncbuf<CharT>&> decltype(auto) ret =
+        sync_buf1.operator=(std::move(sync_buf2));
+  }
+
+  std::basic_stringbuf<CharT> sstr1;
+  std::basic_stringbuf<CharT> sstr2;
+  std::basic_string<CharT> expected(42, CharT('*')); // a long string
+
+  {
+    std::basic_syncbuf<CharT> sync_buf1(&sstr1);
+    sync_buf1.sputc(CharT('A')); // a short string
+
+    std::basic_syncbuf<CharT> sync_buf2(&sstr2);
+    sync_buf2.sputn(expected.data(), expected.size());
+
+#if defined(_LIBCPP_VERSION) && !defined(TEST_HAS_NO_THREADS)
+    assert(std::__wrapped_streambuf_mutex::__instance().__get_count(&sstr1) == 1);
+    assert(std::__wrapped_streambuf_mutex::__instance().__get_count(&sstr2) == 1);
+#endif
+
+    sync_buf2 = std::move(sync_buf1);
+    assert(sync_buf2.get_wrapped() == &sstr1);
+
+    assert(sstr1.str().empty());
+    assert(sstr2.str() == expected);
+
+#if defined(_LIBCPP_VERSION) && !defined(TEST_HAS_NO_THREADS)
+    assert(std::__wrapped_streambuf_mutex::__instance().__get_count(&sstr1) == 1);
+    assert(std::__wrapped_streambuf_mutex::__instance().__get_count(&sstr2) == 0);
+#endif
+  }
+
+  assert(sstr1.str().size() == 1);
+  assert(sstr1.str()[0] == CharT('A'));
+  assert(sstr2.str() == expected);
+}
+
+template <class CharT>
+static void test_short_write_after_assign() {
+  std::basic_stringbuf<CharT> sstr1;
+  std::basic_stringbuf<CharT> sstr2;
+  std::basic_string<CharT> expected(42, CharT('*')); // a long string
+
+  {
+    std::basic_syncbuf<CharT> sync_buf1(&sstr1);
+    sync_buf1.sputc(CharT('A')); // a short string
+
+    std::basic_syncbuf<CharT> sync_buf2(&sstr2);
+    sync_buf2.sputn(expected.data(), expected.size());
+
+    sync_buf2 = std::move(sync_buf1);
+    sync_buf2.sputc(CharT('Z'));
+
+    assert(sstr1.str().empty());
+    assert(sstr2.str() == expected);
+  }
+
+  assert(sstr1.str().size() == 2);
+  assert(sstr1.str()[0] == CharT('A'));
+  assert(sstr1.str()[1] == CharT('Z'));
+  assert(sstr2.str() == expected);
+}
+
+template <class CharT>
+static void test_long_write_after_assign() {
+  std::basic_stringbuf<CharT> sstr1;
+  std::basic_stringbuf<CharT> sstr2;
+  std::basic_string<CharT> expected(42, CharT('*')); // a long string
+
+  {
+    std::basic_syncbuf<CharT> sync_buf1(&sstr1);
+    sync_buf1.sputc(CharT('A')); // a short string
+
+    std::basic_syncbuf<CharT> sync_buf2(&sstr2);
+    sync_buf2.sputn(expected.data(), expected.size());
+
+    sync_buf2 = std::move(sync_buf1);
+    sync_buf2.sputn(expected.data(), expected.size());
+
+    assert(sstr1.str().empty());
+    assert(sstr2.str() == expected);
+  }
+
+  assert(sstr1.str().size() == 1 + expected.size());
+  assert(sstr1.str()[0] == CharT('A'));
+  assert(sstr1.str().substr(1) == expected);
+  assert(sstr2.str() == expected);
+}
+
+template <class CharT>
+static void test_emit_on_assign() {
+  { // don't emit / don't emit
+
+    std::basic_stringbuf<CharT> sstr1;
+    std::basic_stringbuf<CharT> sstr2;
+    std::basic_string<CharT> expected(42, CharT('*')); // a long string
+
+    {
+      std::basic_syncbuf<CharT> sync_buf1(&sstr1);
+      sync_buf1.set_emit_on_sync(false);
+      sync_buf1.sputc(CharT('A')); // a short string
+
+      std::basic_syncbuf<CharT> sync_buf2(&sstr2);
+      sync_buf2.set_emit_on_sync(false);
+      sync_buf2.sputn(expected.data(), expected.size());
+
+      sync_buf2 = std::move(sync_buf1);
+      assert(sstr1.str().empty());
+      assert(sstr2.str() == expected);
+
+      sync_buf2.pubsync();
+      assert(sstr1.str().empty());
+      assert(sstr2.str() == expected);
+    }
+
+    assert(sstr1.str().size() == 1);
+    assert(sstr1.str()[0] == CharT('A'));
+    assert(sstr2.str() == expected);
+  }
+
+  { // don't emit / do emit
+
+    std::basic_stringbuf<CharT> sstr1;
+    std::basic_stringbuf<CharT> sstr2;
+    std::basic_string<CharT> expected(42, CharT('*')); // a long string
+
+    {
+      std::basic_syncbuf<CharT> sync_buf1(&sstr1);
+      sync_buf1.set_emit_on_sync(true);
+      sync_buf1.sputc(CharT('A')); // a short string
+
+      std::basic_syncbuf<CharT> sync_buf2(&sstr2);
+      sync_buf2.set_emit_on_sync(false);
+      sync_buf2.sputn(expected.data(), expected.size());
+
+      sync_buf2 = std::move(sync_buf1);
+      assert(sstr1.str().empty());
+      assert(sstr2.str() == expected);
+
+      sync_buf2.pubsync();
+      assert(sstr1.str().size() == 1);
+      assert(sstr1.str()[0] == CharT('A'));
+      assert(sstr2.str() == expected);
+    }
+
+    assert(sstr1.str().size() == 1);
+    assert(sstr1.str()[0] == CharT('A'));
+    assert(sstr2.str() == expected);
+  }
+
+  { // do emit / don't emit
+
+    std::basic_stringbuf<CharT> sstr1;
+    std::basic_stringbuf<CharT> sstr2;
+    std::basic_string<CharT> expected(42, CharT('*')); // a long string
+
+    {
+      std::basic_syncbuf<CharT> sync_buf1(&sstr1);
+      sync_buf1.set_emit_on_sync(false);
+      sync_buf1.sputc(CharT('A')); // a short string
+
+      std::basic_syncbuf<CharT> sync_buf2(&sstr2);
+      sync_buf2.set_emit_on_sync(true);
+      sync_buf2.sputn(expected.data(), expected.size());
+
+      sync_buf2 = std::move(sync_buf1);
+      assert(sstr1.str().empty());
+      assert(sstr2.str() == expected);
+
+      sync_buf2.pubsync();
+      assert(sstr1.str().empty());
+      assert(sstr2.str() == expected);
+    }
+
+    assert(sstr1.str().size() == 1);
+    assert(sstr1.str()[0] == CharT('A'));
+    assert(sstr2.str() == expected);
+  }
+
+  { // do emit / do emit
+
+    std::basic_stringbuf<CharT> sstr1;
+    std::basic_stringbuf<CharT> sstr2;
+    std::basic_string<CharT> expected(42, CharT('*')); // a long string
+
+    {
+      std::basic_syncbuf<CharT> sync_buf1(&sstr1);
+      sync_buf1.set_emit_on_sync(true);
+      sync_buf1.sputc(CharT('A')); // a short string
+
+      std::basic_syncbuf<CharT> sync_buf2(&sstr2);
+      sync_buf2.set_emit_on_sync(true);
+      sync_buf2.sputn(expected.data(), expected.size());
+
+      sync_buf2 = std::move(sync_buf1);
+      assert(sstr1.str().empty());
+      assert(sstr2.str() == expected);
+
+      sync_buf2.pubsync();
+      assert(sstr1.str().size() == 1);
+      assert(sstr1.str()[0] == CharT('A'));
+      assert(sstr2.str() == expected);
+    }
+
+    assert(sstr1.str().size() == 1);
+    assert(sstr1.str()[0] == CharT('A'));
+    assert(sstr2.str() == expected);
+  }
+}
+
+template <class CharT>
+static void test() {
+  test_assign<CharT>();
+  test_basic<CharT>();
+  test_short_write_after_assign<CharT>();
+  test_long_write_after_assign<CharT>();
+  test_emit_on_assign<CharT>();
+}
+
+int main(int, char**) {
+  test<char>();
+
+#ifndef TEST_HAS_NO_WIDE_CHARACTERS
+  test<wchar_t>();
+#endif
+
+  return 0;
+}

diff  --git a/libcxx/test/std/input.output/syncstream/syncbuf/syncstream.syncbuf.assign/swap.pass.cpp b/libcxx/test/std/input.output/syncstream/syncbuf/syncstream.syncbuf.assign/swap.pass.cpp
new file mode 100644
index 000000000000000..ba007da5a054a7b
--- /dev/null
+++ b/libcxx/test/std/input.output/syncstream/syncbuf/syncstream.syncbuf.assign/swap.pass.cpp
@@ -0,0 +1,274 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17
+// UNSUPPORTED: no-localization
+// UNSUPPORTED: libcpp-has-no-experimental-syncstream
+
+// <syncstream>
+
+// template <class charT, class traits, class Allocator>
+// class basic_syncbuf;
+
+// void swap(basic_syncbuf& other) noexcept;
+
+#include <syncstream>
+#include <sstream>
+#include <cassert>
+
+#include "test_macros.h"
+
+#include <iostream>
+
+template <class CharT>
+static void test_basic() {
+  std::basic_stringbuf<CharT> sstr1;
+  std::basic_stringbuf<CharT> sstr2;
+  std::basic_string<CharT> expected(42, CharT('*')); // a long string
+
+  {
+    std::basic_syncbuf<CharT> sync_buf1(&sstr1);
+    sync_buf1.sputc(CharT('A')); // a short string
+
+    std::basic_syncbuf<CharT> sync_buf2(&sstr2);
+    sync_buf2.sputn(expected.data(), expected.size());
+
+#if defined(_LIBCPP_VERSION) && !defined(TEST_HAS_NO_THREADS)
+    assert(std::__wrapped_streambuf_mutex::__instance().__get_count(&sstr1) == 1);
+    assert(std::__wrapped_streambuf_mutex::__instance().__get_count(&sstr2) == 1);
+#endif
+
+    sync_buf1.swap(sync_buf2);
+    assert(sync_buf1.get_wrapped() == &sstr2);
+    assert(sync_buf2.get_wrapped() == &sstr1);
+
+    assert(sstr1.str().empty());
+    assert(sstr2.str().empty());
+
+#if defined(_LIBCPP_VERSION) && !defined(TEST_HAS_NO_THREADS)
+    assert(std::__wrapped_streambuf_mutex::__instance().__get_count(&sstr1) == 1);
+    assert(std::__wrapped_streambuf_mutex::__instance().__get_count(&sstr2) == 1);
+#endif
+  }
+
+  assert(sstr1.str().size() == 1);
+  assert(sstr1.str()[0] == CharT('A'));
+  assert(sstr2.str() == expected);
+}
+
+template <class CharT>
+static void test_short_write_after_swap() {
+  std::basic_stringbuf<CharT> sstr1;
+  std::basic_stringbuf<CharT> sstr2;
+  std::basic_string<CharT> expected(42, CharT('*')); // a long string
+
+  {
+    std::basic_syncbuf<CharT> sync_buf1(&sstr1);
+    sync_buf1.sputc(CharT('A')); // a short string
+
+    std::basic_syncbuf<CharT> sync_buf2(&sstr2);
+    sync_buf2.sputn(expected.data(), expected.size());
+
+    sync_buf1.swap(sync_buf2);
+    expected.push_back(sync_buf1.sputc(CharT('B')));
+    sync_buf2.sputc(CharT('Z'));
+
+    assert(sstr1.str().empty());
+    assert(sstr2.str().empty());
+  }
+
+  assert(sstr1.str().size() == 2);
+  assert(sstr1.str()[0] == CharT('A'));
+  assert(sstr1.str()[1] == CharT('Z'));
+  assert(sstr2.str() == expected);
+}
+
+template <class CharT>
+static void test_long_write_after_swap() {
+  std::basic_stringbuf<CharT> sstr1;
+  std::basic_stringbuf<CharT> sstr2;
+  std::basic_string<CharT> expected(42, CharT('*')); // a long string
+
+  {
+    std::basic_syncbuf<CharT> sync_buf1(&sstr1);
+    sync_buf1.sputc(CharT('A')); // a short string
+
+    std::basic_syncbuf<CharT> sync_buf2(&sstr2);
+    sync_buf2.sputn(expected.data(), expected.size());
+
+    sync_buf1.swap(sync_buf2);
+    sync_buf1.sputn(expected.data(), expected.size());
+    sync_buf2.sputn(expected.data(), expected.size());
+
+    assert(sstr1.str().empty());
+    assert(sstr2.str().empty());
+  }
+
+  assert(sstr1.str().size() == 1 + expected.size());
+  assert(sstr1.str()[0] == CharT('A'));
+  assert(sstr1.str().substr(1) == expected);
+  assert(sstr2.str() == expected + expected);
+}
+
+template <class CharT>
+static void test_emit_on_sync() {
+  { // false false
+
+    std::basic_stringbuf<CharT> sstr1;
+    std::basic_stringbuf<CharT> sstr2;
+    std::basic_string<CharT> expected(42, CharT('*')); // a long string
+
+    {
+      std::basic_syncbuf<CharT> sync_buf1(&sstr1);
+      sync_buf1.set_emit_on_sync(false);
+      sync_buf1.sputc(CharT('A')); // a short string
+
+      std::basic_syncbuf<CharT> sync_buf2(&sstr2);
+      sync_buf2.set_emit_on_sync(false);
+      sync_buf2.sputn(expected.data(), expected.size());
+
+      sync_buf1.swap(sync_buf2);
+
+      assert(sstr1.str().empty());
+      assert(sstr2.str().empty());
+
+      sync_buf1.pubsync();
+      assert(sstr1.str().empty());
+      assert(sstr2.str().empty());
+
+      sync_buf2.pubsync();
+      assert(sstr1.str().empty());
+      assert(sstr2.str().empty());
+    }
+
+    assert(sstr1.str().size() == 1);
+    assert(sstr1.str()[0] == CharT('A'));
+    assert(sstr2.str() == expected);
+  }
+
+  { // false true
+
+    std::basic_stringbuf<CharT> sstr1;
+    std::basic_stringbuf<CharT> sstr2;
+    std::basic_string<CharT> expected(42, CharT('*')); // a long string
+
+    {
+      std::basic_syncbuf<CharT> sync_buf1(&sstr1);
+      sync_buf1.set_emit_on_sync(true);
+      sync_buf1.sputc(CharT('A')); // a short string
+
+      std::basic_syncbuf<CharT> sync_buf2(&sstr2);
+      sync_buf2.set_emit_on_sync(false);
+      sync_buf2.sputn(expected.data(), expected.size());
+
+      sync_buf1.swap(sync_buf2);
+
+      assert(sstr1.str().empty());
+      assert(sstr2.str().empty());
+
+      sync_buf1.pubsync();
+      assert(sstr1.str().empty());
+      assert(sstr2.str().empty());
+
+      sync_buf2.pubsync();
+      assert(sstr1.str().size() == 1);
+      assert(sstr1.str()[0] == CharT('A'));
+      assert(sstr2.str().empty());
+    }
+
+    assert(sstr1.str().size() == 1);
+    assert(sstr1.str()[0] == CharT('A'));
+    assert(sstr2.str() == expected);
+  }
+
+  { // true false
+
+    std::basic_stringbuf<CharT> sstr1;
+    std::basic_stringbuf<CharT> sstr2;
+    std::basic_string<CharT> expected(42, CharT('*')); // a long string
+
+    {
+      std::basic_syncbuf<CharT> sync_buf1(&sstr1);
+      sync_buf1.set_emit_on_sync(false);
+      sync_buf1.sputc(CharT('A')); // a short string
+
+      std::basic_syncbuf<CharT> sync_buf2(&sstr2);
+      sync_buf2.set_emit_on_sync(true);
+      sync_buf2.sputn(expected.data(), expected.size());
+
+      sync_buf1.swap(sync_buf2);
+
+      assert(sstr1.str().empty());
+      assert(sstr2.str().empty());
+
+      sync_buf1.pubsync();
+      assert(sstr1.str().empty());
+      assert(sstr2.str() == expected);
+
+      sync_buf2.pubsync();
+      assert(sstr1.str().empty());
+      assert(sstr2.str() == expected);
+    }
+
+    assert(sstr1.str().size() == 1);
+    assert(sstr1.str()[0] == CharT('A'));
+    assert(sstr2.str() == expected);
+  }
+
+  { // true true
+
+    std::basic_stringbuf<CharT> sstr1;
+    std::basic_stringbuf<CharT> sstr2;
+    std::basic_string<CharT> expected(42, CharT('*')); // a long string
+
+    {
+      std::basic_syncbuf<CharT> sync_buf1(&sstr1);
+      sync_buf1.set_emit_on_sync(true);
+      sync_buf1.sputc(CharT('A')); // a short string
+
+      std::basic_syncbuf<CharT> sync_buf2(&sstr2);
+      sync_buf2.set_emit_on_sync(true);
+      sync_buf2.sputn(expected.data(), expected.size());
+
+      sync_buf1.swap(sync_buf2);
+
+      assert(sstr1.str().empty());
+      assert(sstr2.str().empty());
+
+      sync_buf1.pubsync();
+      assert(sstr1.str().empty());
+      assert(sstr2.str() == expected);
+
+      sync_buf2.pubsync();
+      assert(sstr1.str().size() == 1);
+      assert(sstr1.str()[0] == CharT('A'));
+      assert(sstr2.str() == expected);
+    }
+
+    assert(sstr1.str().size() == 1);
+    assert(sstr1.str()[0] == CharT('A'));
+    assert(sstr2.str() == expected);
+  }
+}
+
+template <class CharT>
+static void test() {
+  test_basic<CharT>();
+  test_emit_on_sync<CharT>();
+  test_short_write_after_swap<CharT>();
+  test_long_write_after_swap<CharT>();
+}
+
+int main(int, char**) {
+  test<char>();
+#ifndef TEST_HAS_NO_WIDE_CHARACTERS
+  test<wchar_t>();
+#endif
+
+  return 0;
+}

diff  --git a/libcxx/test/std/input.output/syncstream/syncbuf/syncstream.syncbuf.cons/cons.default.pass.cpp b/libcxx/test/std/input.output/syncstream/syncbuf/syncstream.syncbuf.cons/cons.default.pass.cpp
new file mode 100644
index 000000000000000..aa0eb2d41e0f014
--- /dev/null
+++ b/libcxx/test/std/input.output/syncstream/syncbuf/syncstream.syncbuf.cons/cons.default.pass.cpp
@@ -0,0 +1,60 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17
+// UNSUPPORTED: no-localization
+// UNSUPPORTED: libcpp-has-no-experimental-syncstream
+
+// <syncstream>
+
+// template <class charT, class traits, class Allocator>
+// class basic_syncbuf;
+
+// basic_syncbuf();
+
+#include <cassert>
+#include <concepts>
+#include <syncstream>
+
+#include "test_macros.h"
+#include "constexpr_char_traits.h"
+#include "test_allocator.h"
+
+template <class CharT>
+void test() {
+  {
+    using Buf = std::basic_syncbuf<CharT>;
+    static_assert(std::default_initializable<Buf>);
+    Buf buf;
+    assert(buf.get_wrapped() == nullptr);
+    assert(buf.get_allocator() == std::allocator<CharT>());
+  }
+  {
+    using Buf = std::basic_syncbuf<CharT, constexpr_char_traits<CharT>>;
+    static_assert(std::default_initializable<Buf>);
+    Buf buf;
+    assert(buf.get_wrapped() == nullptr);
+    assert(buf.get_allocator() == std::allocator<CharT>());
+  }
+  {
+    using Buf = std::basic_syncbuf<CharT, constexpr_char_traits<CharT>, test_allocator<CharT>>;
+    static_assert(std::default_initializable<Buf>);
+    Buf buf;
+    assert(buf.get_wrapped() == nullptr);
+    assert(buf.get_allocator() == test_allocator<CharT>());
+  }
+}
+
+int main(int, char**) {
+  test<char>();
+#ifndef TEST_HAS_NO_WIDE_CHARACTERS
+  test<wchar_t>();
+#endif
+
+  return 0;
+}

diff  --git a/libcxx/test/std/input.output/syncstream/syncbuf/syncstream.syncbuf.cons/cons.move.pass.cpp b/libcxx/test/std/input.output/syncstream/syncbuf/syncstream.syncbuf.cons/cons.move.pass.cpp
new file mode 100644
index 000000000000000..d12b3d0f9ff9f3d
--- /dev/null
+++ b/libcxx/test/std/input.output/syncstream/syncbuf/syncstream.syncbuf.cons/cons.move.pass.cpp
@@ -0,0 +1,136 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17
+// UNSUPPORTED: no-localization
+// UNSUPPORTED: libcpp-has-no-experimental-syncstream
+
+// <syncstream>
+
+// template <class charT, class traits, class Allocator>
+// class basic_syncbuf;
+
+// basic_syncbuf(basic_syncbuf&& other);
+
+#include <syncstream>
+#include <cassert>
+
+#include "test_macros.h"
+#include "constexpr_char_traits.h"
+#include "test_allocator.h"
+
+template <class CharT>
+void test() {
+  {
+    using Buf = std::basic_syncbuf<CharT>;
+    const std::allocator<CharT> alloc;
+    {
+      Buf b1{nullptr, alloc};
+      Buf b2{std::move(b1)};
+
+      assert(b2.get_wrapped() == nullptr);
+      assert(b2.get_allocator() == alloc);
+    }
+    {
+      Buf w;
+#if defined(_LIBCPP_VERSION) && !defined(TEST_HAS_NO_THREADS)
+      assert(std::__wrapped_streambuf_mutex::__instance().__get_count(&w) == 0);
+#endif
+      {
+        Buf b1{&w, alloc};
+#if defined(_LIBCPP_VERSION) && !defined(TEST_HAS_NO_THREADS)
+        assert(std::__wrapped_streambuf_mutex::__instance().__get_count(&w) == 1);
+#endif
+        Buf b2{std::move(b1)};
+#if defined(_LIBCPP_VERSION) && !defined(TEST_HAS_NO_THREADS)
+        assert(std::__wrapped_streambuf_mutex::__instance().__get_count(&w) == 1);
+#endif
+        assert(b2.get_wrapped() == &w);
+        assert(b2.get_allocator() == alloc);
+      }
+#if defined(_LIBCPP_VERSION) && !defined(TEST_HAS_NO_THREADS)
+      assert(std::__wrapped_streambuf_mutex::__instance().__get_count(&w) == 0);
+#endif
+    }
+  }
+
+  {
+    using Buf = std::basic_syncbuf<CharT, constexpr_char_traits<CharT>>;
+    const std::allocator<CharT> alloc;
+    {
+      Buf b1{nullptr, alloc};
+      Buf b2{std::move(b1)};
+
+      assert(b2.get_wrapped() == nullptr);
+      assert(b2.get_allocator() == alloc);
+    }
+    {
+      Buf w;
+#if defined(_LIBCPP_VERSION) && !defined(TEST_HAS_NO_THREADS)
+      assert(std::__wrapped_streambuf_mutex::__instance().__get_count(&w) == 0);
+#endif
+      {
+        Buf b1{&w, alloc};
+#if defined(_LIBCPP_VERSION) && !defined(TEST_HAS_NO_THREADS)
+        assert(std::__wrapped_streambuf_mutex::__instance().__get_count(&w) == 1);
+#endif
+        Buf b2{std::move(b1)};
+#if defined(_LIBCPP_VERSION) && !defined(TEST_HAS_NO_THREADS)
+        assert(std::__wrapped_streambuf_mutex::__instance().__get_count(&w) == 1);
+#endif
+        assert(b2.get_wrapped() == &w);
+        assert(b2.get_allocator() == alloc);
+      }
+#if defined(_LIBCPP_VERSION) && !defined(TEST_HAS_NO_THREADS)
+      assert(std::__wrapped_streambuf_mutex::__instance().__get_count(&w) == 0);
+#endif
+    }
+  }
+
+  {
+    using Buf = std::basic_syncbuf<CharT, constexpr_char_traits<CharT>, test_allocator<CharT>>;
+    test_allocator<CharT> alloc{42};
+    {
+      Buf b1{nullptr, alloc};
+      Buf b2{std::move(b1)};
+
+      assert(b2.get_wrapped() == nullptr);
+      assert(b2.get_allocator() == alloc);
+    }
+    {
+      Buf w;
+#if defined(_LIBCPP_VERSION) && !defined(TEST_HAS_NO_THREADS)
+      assert(std::__wrapped_streambuf_mutex::__instance().__get_count(&w) == 0);
+#endif
+      {
+        Buf b1{&w, alloc};
+#if defined(_LIBCPP_VERSION) && !defined(TEST_HAS_NO_THREADS)
+        assert(std::__wrapped_streambuf_mutex::__instance().__get_count(&w) == 1);
+#endif
+        Buf b2{std::move(b1)};
+#if defined(_LIBCPP_VERSION) && !defined(TEST_HAS_NO_THREADS)
+        assert(std::__wrapped_streambuf_mutex::__instance().__get_count(&w) == 1);
+#endif
+        assert(b2.get_wrapped() == &w);
+        assert(b2.get_allocator() == alloc);
+      }
+#if defined(_LIBCPP_VERSION) && !defined(TEST_HAS_NO_THREADS)
+      assert(std::__wrapped_streambuf_mutex::__instance().__get_count(&w) == 0);
+#endif
+    }
+  }
+}
+
+int main(int, char**) {
+  test<char>();
+#ifndef TEST_HAS_NO_WIDE_CHARACTERS
+  test<wchar_t>();
+#endif
+
+  return 0;
+}

diff  --git a/libcxx/test/std/input.output/syncstream/syncbuf/syncstream.syncbuf.cons/cons.pointer.allocator.pass.cpp b/libcxx/test/std/input.output/syncstream/syncbuf/syncstream.syncbuf.cons/cons.pointer.allocator.pass.cpp
new file mode 100644
index 000000000000000..faecfa9ac58faf3
--- /dev/null
+++ b/libcxx/test/std/input.output/syncstream/syncbuf/syncstream.syncbuf.cons/cons.pointer.allocator.pass.cpp
@@ -0,0 +1,119 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17
+// UNSUPPORTED: no-localization
+// UNSUPPORTED: libcpp-has-no-experimental-syncstream
+
+// <syncstream>
+
+// template <class charT, class traits, class Allocator>
+// class basic_syncbuf;
+
+// basic_syncbuf(streambuf_type* obuf, const Allocator&);
+
+#include <cassert>
+#include <concepts>
+#include <syncstream>
+
+#include "test_macros.h"
+#include "constexpr_char_traits.h"
+#include "test_allocator.h"
+
+template <class CharT>
+void test() {
+  {
+    using Buf = std::basic_syncbuf<CharT>;
+    const std::allocator<CharT> alloc;
+    {
+      Buf buf = {nullptr, alloc};
+      assert(buf.get_wrapped() == nullptr);
+      assert(buf.get_allocator() == alloc);
+    }
+    {
+      Buf w;
+#if defined(_LIBCPP_VERSION) && !defined(TEST_HAS_NO_THREADS)
+      assert(std::__wrapped_streambuf_mutex::__instance().__get_count(&w) == 0);
+#endif
+      {
+        Buf buf = {&w, alloc};
+#if defined(_LIBCPP_VERSION) && !defined(TEST_HAS_NO_THREADS)
+        assert(std::__wrapped_streambuf_mutex::__instance().__get_count(&w) == 1);
+#endif
+        assert(buf.get_wrapped() == &w);
+        assert(buf.get_allocator() == alloc);
+      }
+#if defined(_LIBCPP_VERSION) && !defined(TEST_HAS_NO_THREADS)
+      assert(std::__wrapped_streambuf_mutex::__instance().__get_count(&w) == 0);
+#endif
+    }
+  }
+
+  {
+    using Buf = std::basic_syncbuf<CharT, constexpr_char_traits<CharT>>;
+    const std::allocator<CharT> alloc;
+    {
+      Buf buf = {nullptr, alloc};
+      assert(buf.get_wrapped() == nullptr);
+      assert(buf.get_allocator() == alloc);
+    }
+    {
+      Buf w;
+#if defined(_LIBCPP_VERSION) && !defined(TEST_HAS_NO_THREADS)
+      assert(std::__wrapped_streambuf_mutex::__instance().__get_count(&w) == 0);
+#endif
+      {
+        Buf buf{&w, alloc};
+#if defined(_LIBCPP_VERSION) && !defined(TEST_HAS_NO_THREADS)
+        assert(std::__wrapped_streambuf_mutex::__instance().__get_count(&w) == 1);
+#endif
+        assert(buf.get_wrapped() == &w);
+        assert(buf.get_allocator() == alloc);
+      }
+#if defined(_LIBCPP_VERSION) && !defined(TEST_HAS_NO_THREADS)
+      assert(std::__wrapped_streambuf_mutex::__instance().__get_count(&w) == 0);
+#endif
+    }
+  }
+
+  {
+    using Buf = std::basic_syncbuf<CharT, constexpr_char_traits<CharT>, test_allocator<CharT>>;
+    test_allocator<CharT> alloc{42};
+    {
+      Buf buf = {nullptr, alloc};
+      assert(buf.get_wrapped() == nullptr);
+      assert(buf.get_allocator() == alloc);
+    }
+    {
+      Buf w;
+#if defined(_LIBCPP_VERSION) && !defined(TEST_HAS_NO_THREADS)
+      assert(std::__wrapped_streambuf_mutex::__instance().__get_count(&w) == 0);
+#endif
+      {
+        Buf buf{&w, alloc};
+#if defined(_LIBCPP_VERSION) && !defined(TEST_HAS_NO_THREADS)
+        assert(std::__wrapped_streambuf_mutex::__instance().__get_count(&w) == 1);
+#endif
+        assert(buf.get_wrapped() == &w);
+        assert(buf.get_allocator() == alloc);
+      }
+#if defined(_LIBCPP_VERSION) && !defined(TEST_HAS_NO_THREADS)
+      assert(std::__wrapped_streambuf_mutex::__instance().__get_count(&w) == 0);
+#endif
+    }
+  }
+}
+
+int main(int, char**) {
+  test<char>();
+#ifndef TEST_HAS_NO_WIDE_CHARACTERS
+  test<wchar_t>();
+#endif
+
+  return 0;
+}

diff  --git a/libcxx/test/std/input.output/syncstream/syncbuf/syncstream.syncbuf.cons/cons.pointer.pass.cpp b/libcxx/test/std/input.output/syncstream/syncbuf/syncstream.syncbuf.cons/cons.pointer.pass.cpp
new file mode 100644
index 000000000000000..ed5eff75737e79c
--- /dev/null
+++ b/libcxx/test/std/input.output/syncstream/syncbuf/syncstream.syncbuf.cons/cons.pointer.pass.cpp
@@ -0,0 +1,128 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17
+// UNSUPPORTED: no-localization
+// UNSUPPORTED: libcpp-has-no-experimental-syncstream
+
+// <syncstream>
+
+// template <class charT, class traits, class Allocator>
+// class basic_syncbuf;
+
+// explicit basic_syncbuf(streambuf_type* obuf);
+
+#include <cassert>
+#include <concepts>
+#include <syncstream>
+
+#include "test_macros.h"
+#include "constexpr_char_traits.h"
+#include "test_allocator.h"
+
+template <class CharT>
+void test() {
+  {
+    using Buf = std::basic_syncbuf<CharT>;
+
+    static_assert(!std::convertible_to<std::basic_streambuf<CharT>*, Buf>);
+    static_assert(std::constructible_from<Buf, std::basic_streambuf<CharT>*>);
+
+    {
+      Buf buf{nullptr};
+      assert(buf.get_wrapped() == nullptr);
+      assert(buf.get_allocator() == std::allocator<CharT>());
+    }
+    {
+      Buf w;
+#if defined(_LIBCPP_VERSION) && !defined(TEST_HAS_NO_THREADS)
+      assert(std::__wrapped_streambuf_mutex::__instance().__get_count(&w) == 0);
+#endif
+      {
+        Buf buf{&w};
+#if defined(_LIBCPP_VERSION) && !defined(TEST_HAS_NO_THREADS)
+        assert(std::__wrapped_streambuf_mutex::__instance().__get_count(&w) == 1);
+#endif
+        assert(buf.get_wrapped() == &w);
+        assert(buf.get_allocator() == std::allocator<CharT>());
+      }
+#if defined(_LIBCPP_VERSION) && !defined(TEST_HAS_NO_THREADS)
+      assert(std::__wrapped_streambuf_mutex::__instance().__get_count(&w) == 0);
+#endif
+    }
+  }
+
+  {
+    using Buf = std::basic_syncbuf<CharT, constexpr_char_traits<CharT>>;
+
+    static_assert(!std::convertible_to<std::basic_streambuf<CharT, constexpr_char_traits<CharT>>*, Buf>);
+    static_assert(std::constructible_from<Buf, std::basic_streambuf<CharT, constexpr_char_traits<CharT>>*>);
+
+    {
+      Buf buf{nullptr};
+      assert(buf.get_wrapped() == nullptr);
+      assert(buf.get_allocator() == std::allocator<CharT>());
+    }
+    {
+      Buf w;
+#if defined(_LIBCPP_VERSION) && !defined(TEST_HAS_NO_THREADS)
+      assert(std::__wrapped_streambuf_mutex::__instance().__get_count(&w) == 0);
+#endif
+      {
+        Buf buf{&w};
+#if defined(_LIBCPP_VERSION) && !defined(TEST_HAS_NO_THREADS)
+        assert(std::__wrapped_streambuf_mutex::__instance().__get_count(&w) == 1);
+#endif
+        assert(buf.get_wrapped() == &w);
+        assert(buf.get_allocator() == std::allocator<CharT>());
+      }
+#if defined(_LIBCPP_VERSION) && !defined(TEST_HAS_NO_THREADS)
+      assert(std::__wrapped_streambuf_mutex::__instance().__get_count(&w) == 0);
+#endif
+    }
+  }
+
+  {
+    using Buf = std::basic_syncbuf<CharT, constexpr_char_traits<CharT>, test_allocator<CharT>>;
+
+    static_assert(!std::convertible_to<std::basic_streambuf<CharT, constexpr_char_traits<CharT>>*, Buf>);
+    static_assert(std::constructible_from<Buf, std::basic_streambuf<CharT, constexpr_char_traits<CharT>>*>);
+
+    {
+      Buf buf{nullptr};
+      assert(buf.get_wrapped() == nullptr);
+      assert(buf.get_allocator() == test_allocator<CharT>());
+    }
+    {
+      Buf w;
+#if defined(_LIBCPP_VERSION) && !defined(TEST_HAS_NO_THREADS)
+      assert(std::__wrapped_streambuf_mutex::__instance().__get_count(&w) == 0);
+#endif
+      {
+        Buf buf{&w};
+#if defined(_LIBCPP_VERSION) && !defined(TEST_HAS_NO_THREADS)
+        assert(std::__wrapped_streambuf_mutex::__instance().__get_count(&w) == 1);
+#endif
+        assert(buf.get_wrapped() == &w);
+        assert(buf.get_allocator() == test_allocator<CharT>());
+      }
+#if defined(_LIBCPP_VERSION) && !defined(TEST_HAS_NO_THREADS)
+      assert(std::__wrapped_streambuf_mutex::__instance().__get_count(&w) == 0);
+#endif
+    }
+  }
+}
+
+int main(int, char**) {
+  test<char>();
+#ifndef TEST_HAS_NO_WIDE_CHARACTERS
+  test<wchar_t>();
+#endif
+
+  return 0;
+}

diff  --git a/libcxx/test/std/input.output/syncstream/syncbuf/syncstream.syncbuf.cons/dtor.pass.cpp b/libcxx/test/std/input.output/syncstream/syncbuf/syncstream.syncbuf.cons/dtor.pass.cpp
new file mode 100644
index 000000000000000..57ab447d96ccf25
--- /dev/null
+++ b/libcxx/test/std/input.output/syncstream/syncbuf/syncstream.syncbuf.cons/dtor.pass.cpp
@@ -0,0 +1,54 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17
+// UNSUPPORTED: no-localization
+// UNSUPPORTED: libcpp-has-no-experimental-syncstream
+
+// <syncstream>
+
+// template <class charT, class traits, class Allocator>
+// class basic_syncbuf;
+
+// ~basic_syncbuf();
+
+#include <syncstream>
+#include <cassert>
+
+#include "../helpers.h"
+#include "test_macros.h"
+
+template <class CharT>
+void test() {
+  // We do this because we want to be able to use CharT
+  CharT arr[3] = {'a', 'b', 'c'};
+  CharT* ptr   = arr;
+
+  test_buf<CharT> base;
+  const std::allocator<CharT> alloc;
+  {
+    test_syncbuf<CharT> buf(&base, alloc);
+
+    buf._setp(ptr, ptr + 3);
+    assert(base._pptr() == nullptr);
+    // The destructor calls buf.emit();
+  }
+  CharT* pptr = base._pptr();
+  while (pptr) {
+    assert(*pptr++ == *ptr++);
+  }
+}
+
+int main(int, char**) {
+  test<char>();
+#ifndef TEST_HAS_NO_WIDE_CHARACTERS
+  test<wchar_t>();
+#endif
+
+  return 0;
+}

diff  --git a/libcxx/test/std/input.output/syncstream/syncbuf/syncstream.syncbuf.members/emit.pass.cpp b/libcxx/test/std/input.output/syncstream/syncbuf/syncstream.syncbuf.members/emit.pass.cpp
new file mode 100644
index 000000000000000..8905b0494d8f500
--- /dev/null
+++ b/libcxx/test/std/input.output/syncstream/syncbuf/syncstream.syncbuf.members/emit.pass.cpp
@@ -0,0 +1,52 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17
+// UNSUPPORTED: no-localization
+// UNSUPPORTED: libcpp-has-no-experimental-syncstream
+
+// <syncstream>
+
+// template <class charT, class traits, class Allocator>
+// class basic_syncbuf;
+
+// bool emit();
+
+#include <syncstream>
+#include <cassert>
+
+#include "../helpers.h"
+#include "test_macros.h"
+
+template <class CharT>
+void test() {
+  // We do this because we want to be able to use CharT
+  CharT arr[3] = {'a', 'b', 'c'};
+  CharT* ptr   = arr;
+
+  test_buf<CharT> base;
+  const std::allocator<CharT> alloc;
+  test_syncbuf<CharT> buff(&base, alloc);
+
+  buff._setp(ptr, ptr + 3);
+  assert(base._pptr() == nullptr);
+  buff.emit();
+  CharT* pptr = base._pptr();
+  while (pptr) {
+    assert(*pptr++ == *ptr++);
+  }
+}
+
+int main(int, char**) {
+  test<char>();
+#ifndef TEST_HAS_NO_WIDE_CHARACTERS
+  test<wchar_t>();
+#endif
+
+  return 0;
+}

diff  --git a/libcxx/test/std/input.output/syncstream/syncbuf/syncstream.syncbuf.members/get_allocator.pass.cpp b/libcxx/test/std/input.output/syncstream/syncbuf/syncstream.syncbuf.members/get_allocator.pass.cpp
new file mode 100644
index 000000000000000..d722acea0b3796f
--- /dev/null
+++ b/libcxx/test/std/input.output/syncstream/syncbuf/syncstream.syncbuf.members/get_allocator.pass.cpp
@@ -0,0 +1,42 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17
+// UNSUPPORTED: no-localization
+// UNSUPPORTED: libcpp-has-no-experimental-syncstream
+
+// <syncstream>
+
+// template <class charT, class traits, class Allocator>
+// class basic_syncbuf;
+
+// allocator_type get_allocator() const noexcept;
+
+#include <syncstream>
+#include <cassert>
+
+#include "test_macros.h"
+#include "../helpers.h"
+
+template <class T>
+void test_get_allocator() {
+  test_buf<T> base;
+  test_allocator<T> alloc(42);
+  const test_syncbuf<T, test_allocator<T>> buff(&base, alloc);
+  assert(buff.get_allocator().id == 42);
+  ASSERT_NOEXCEPT(buff.get_allocator());
+}
+
+int main(int, char**) {
+  test_get_allocator<char>();
+#ifndef TEST_HAS_NO_WIDE_CHARACTERS
+  test_get_allocator<wchar_t>();
+#endif
+
+  return 0;
+}

diff  --git a/libcxx/test/std/input.output/syncstream/syncbuf/syncstream.syncbuf.members/get_wrapped.pass.cpp b/libcxx/test/std/input.output/syncstream/syncbuf/syncstream.syncbuf.members/get_wrapped.pass.cpp
new file mode 100644
index 000000000000000..b8443ac5e523d8f
--- /dev/null
+++ b/libcxx/test/std/input.output/syncstream/syncbuf/syncstream.syncbuf.members/get_wrapped.pass.cpp
@@ -0,0 +1,44 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17
+// UNSUPPORTED: no-localization
+// UNSUPPORTED: libcpp-has-no-experimental-syncstream
+
+// <syncstream>
+
+// template <class charT, class traits, class Allocator>
+// class basic_syncbuf;
+
+// streambuf_type* get_wrapped() const noexcept;
+
+#include <syncstream>
+#include <concepts>
+#include <cassert>
+
+#include "../helpers.h"
+#include "test_macros.h"
+
+template <class T>
+void test() {
+  test_buf<T> base(42);
+  const std::allocator<T> alloc;
+  const test_syncbuf<T> buff(&base, alloc);
+  std::same_as<std::basic_streambuf<T>*> auto wrapped = buff.get_wrapped();
+  assert(static_cast<test_buf<T>*>(wrapped)->id == 42);
+  ASSERT_NOEXCEPT(buff.get_wrapped());
+}
+
+int main(int, char**) {
+  test<char>();
+#ifndef TEST_HAS_NO_WIDE_CHARACTERS
+  test<wchar_t>();
+#endif
+
+  return 0;
+}

diff  --git a/libcxx/test/std/input.output/syncstream/syncbuf/syncstream.syncbuf.members/set_emit_on_sync.pass.cpp b/libcxx/test/std/input.output/syncstream/syncbuf/syncstream.syncbuf.members/set_emit_on_sync.pass.cpp
new file mode 100644
index 000000000000000..7630e7e87c1e128
--- /dev/null
+++ b/libcxx/test/std/input.output/syncstream/syncbuf/syncstream.syncbuf.members/set_emit_on_sync.pass.cpp
@@ -0,0 +1,42 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17
+// UNSUPPORTED: no-localization
+// UNSUPPORTED: libcpp-has-no-experimental-syncstream
+
+// <syncstream>
+
+// template <class charT, class traits, class Allocator>
+// class basic_syncbuf;
+
+// void set_emit_on_sync(bool) noexcept;
+
+#include <syncstream>
+#include <cassert>
+
+#include "test_macros.h"
+#include "../helpers.h"
+
+template <class T>
+void test_set_emit_on_sync() {
+  // set_emit_on_sync tested in sync, which is called by pubsync. The assign
+  // and swap test use this.
+  test_syncbuf<T, std::allocator<T>> buff(nullptr, std::allocator<T>());
+  ASSERT_NOEXCEPT(buff.set_emit_on_sync(false));
+  buff.set_emit_on_sync(false); // Validates the function can be called.
+}
+
+int main(int, char**) {
+  test_set_emit_on_sync<char>();
+#ifndef TEST_HAS_NO_WIDE_CHARACTERS
+  test_set_emit_on_sync<wchar_t>();
+#endif
+
+  return 0;
+}

diff  --git a/libcxx/test/std/input.output/syncstream/syncbuf/syncstream.syncbuf.special/swap.pass.cpp b/libcxx/test/std/input.output/syncstream/syncbuf/syncstream.syncbuf.special/swap.pass.cpp
new file mode 100644
index 000000000000000..d1ec4711f3f499a
--- /dev/null
+++ b/libcxx/test/std/input.output/syncstream/syncbuf/syncstream.syncbuf.special/swap.pass.cpp
@@ -0,0 +1,43 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17
+// UNSUPPORTED: no-localization
+// UNSUPPORTED: libcpp-has-no-experimental-syncstream
+
+// <syncstream>
+
+//  template<class charT, class traits, class Allocator>
+//    void swap(basic_syncbuf<charT, traits, Allocator>&,
+//              basic_syncbuf<charT, traits, Allocator>&);
+
+#include <syncstream>
+#include <cassert>
+
+#include "test_macros.h"
+
+template <class CharT>
+void test() {
+  std::basic_syncbuf<CharT> base1;
+  std::basic_syncbuf<CharT> base2;
+  std::basic_syncbuf<CharT> buff1(&base1);
+  std::basic_syncbuf<CharT> buff2(&base2);
+  std::swap(buff1, buff2);
+
+  assert(buff1.get_wrapped() == &base2);
+  assert(buff2.get_wrapped() == &base1);
+}
+
+int main(int, char**) {
+  test<char>();
+#ifndef TEST_HAS_NO_WIDE_CHARACTERS
+  test<wchar_t>();
+#endif
+
+  return 0;
+}

diff  --git a/libcxx/test/std/input.output/syncstream/syncbuf/types.compile.pass.cpp b/libcxx/test/std/input.output/syncstream/syncbuf/types.compile.pass.cpp
new file mode 100644
index 000000000000000..5fe11775505513f
--- /dev/null
+++ b/libcxx/test/std/input.output/syncstream/syncbuf/types.compile.pass.cpp
@@ -0,0 +1,84 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17
+// UNSUPPORTED: no-localization
+// UNSUPPORTED: libcpp-has-no-experimental-syncstream
+
+// <syncstream>
+
+// template<class charT, class traits = char_traits<charT>, class Allocator = allocator<charT>>
+//   class basic_syncbuf {
+//
+//  public:
+//    using char_type      = charT;
+//    using int_type       = typename traits::int_type;
+//    using pos_type       = typename traits::pos_type;
+//    using off_type       = typename traits::off_type;
+//    using traits_type    = traits;
+//    using allocator_type = Allocator;
+//
+//    using streambuf_type = basic_streambuf<charT, traits>;
+//
+//  ...
+
+#include <syncstream>
+#include <concepts>
+
+#include "test_macros.h"
+#include "constexpr_char_traits.h"
+#include "test_allocator.h"
+
+static_assert(
+    std::same_as<std::basic_syncbuf<char>, std::basic_syncbuf<char, std::char_traits<char>, std::allocator<char>>>);
+static_assert(std::same_as<std::basic_syncbuf<char, constexpr_char_traits<char>>,
+                           std::basic_syncbuf<char, constexpr_char_traits<char>, std::allocator<char>>>);
+
+static_assert(
+    std::same_as<std::basic_syncbuf<char, constexpr_char_traits<char>, test_allocator<char>>::char_type, char>);
+static_assert(std::same_as<std::basic_syncbuf<char, constexpr_char_traits<char>, test_allocator<char>>::int_type,
+                           constexpr_char_traits<char>::int_type>);
+static_assert(std::same_as<std::basic_syncbuf<char, constexpr_char_traits<char>, test_allocator<char>>::pos_type,
+                           constexpr_char_traits<char>::pos_type>);
+static_assert(std::same_as<std::basic_syncbuf<char, constexpr_char_traits<char>, test_allocator<char>>::off_type,
+                           constexpr_char_traits<char>::off_type>);
+static_assert(std::same_as<std::basic_syncbuf<char, constexpr_char_traits<char>, test_allocator<char>>::traits_type,
+                           constexpr_char_traits<char>>);
+static_assert(std::same_as<std::basic_syncbuf<char, constexpr_char_traits<char>, test_allocator<char>>::allocator_type,
+                           test_allocator<char>>);
+static_assert(std::same_as<std::basic_syncbuf<char, constexpr_char_traits<char>, test_allocator<char>>::streambuf_type,
+                           std::basic_streambuf<char, constexpr_char_traits<char>>>);
+
+#ifndef TEST_HAS_NO_WIDE_CHARACTERS
+static_assert(std::same_as<std::basic_syncbuf<wchar_t>,
+                           std::basic_syncbuf<wchar_t, std::char_traits<wchar_t>, std::allocator<wchar_t>>>);
+static_assert(std::same_as<std::basic_syncbuf<wchar_t, constexpr_char_traits<wchar_t>>,
+                           std::basic_syncbuf<wchar_t, constexpr_char_traits<wchar_t>, std::allocator<wchar_t>>>);
+
+static_assert(
+    std::same_as<std::basic_syncbuf<wchar_t, constexpr_char_traits<wchar_t>, test_allocator<wchar_t>>::char_type,
+                 wchar_t>);
+static_assert(
+    std::same_as<std::basic_syncbuf<wchar_t, constexpr_char_traits<wchar_t>, test_allocator<wchar_t>>::int_type,
+                 constexpr_char_traits<wchar_t>::int_type>);
+static_assert(
+    std::same_as<std::basic_syncbuf<wchar_t, constexpr_char_traits<wchar_t>, test_allocator<wchar_t>>::pos_type,
+                 constexpr_char_traits<wchar_t>::pos_type>);
+static_assert(
+    std::same_as<std::basic_syncbuf<wchar_t, constexpr_char_traits<wchar_t>, test_allocator<wchar_t>>::off_type,
+                 constexpr_char_traits<wchar_t>::off_type>);
+static_assert(
+    std::same_as<std::basic_syncbuf<wchar_t, constexpr_char_traits<wchar_t>, test_allocator<wchar_t>>::traits_type,
+                 constexpr_char_traits<wchar_t>>);
+static_assert(
+    std::same_as<std::basic_syncbuf<wchar_t, constexpr_char_traits<wchar_t>, test_allocator<wchar_t>>::allocator_type,
+                 test_allocator<wchar_t>>);
+static_assert(
+    std::same_as<std::basic_syncbuf<wchar_t, constexpr_char_traits<wchar_t>, test_allocator<wchar_t>>::streambuf_type,
+                 std::basic_streambuf<wchar_t, constexpr_char_traits<wchar_t>>>);
+#endif // TEST_HAS_NO_WIDE_CHARACTERS

diff  --git a/libcxx/test/std/language.support/support.limits/support.limits.general/syncstream.version.compile.pass.cpp b/libcxx/test/std/language.support/support.limits/support.limits.general/syncstream.version.compile.pass.cpp
new file mode 100644
index 000000000000000..88769d2770b1bd5
--- /dev/null
+++ b/libcxx/test/std/language.support/support.limits/support.limits.general/syncstream.version.compile.pass.cpp
@@ -0,0 +1,91 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// WARNING: This test was generated by generate_feature_test_macro_components.py
+// and should not be edited manually.
+//
+// clang-format off
+
+// UNSUPPORTED: no-localization
+
+// <syncstream>
+
+// Test the feature test macros defined by <syncstream>
+
+/*  Constant             Value
+    __cpp_lib_syncbuf    201803L [C++20]
+*/
+
+#include <syncstream>
+#include "test_macros.h"
+
+#if TEST_STD_VER < 14
+
+# ifdef __cpp_lib_syncbuf
+#   error "__cpp_lib_syncbuf should not be defined before c++20"
+# endif
+
+#elif TEST_STD_VER == 14
+
+# ifdef __cpp_lib_syncbuf
+#   error "__cpp_lib_syncbuf should not be defined before c++20"
+# endif
+
+#elif TEST_STD_VER == 17
+
+# ifdef __cpp_lib_syncbuf
+#   error "__cpp_lib_syncbuf should not be defined before c++20"
+# endif
+
+#elif TEST_STD_VER == 20
+
+# if !defined(_LIBCPP_HAS_NO_EXPERIMENTAL_SYNCSTREAM)
+#   ifndef __cpp_lib_syncbuf
+#     error "__cpp_lib_syncbuf should be defined in c++20"
+#   endif
+#   if __cpp_lib_syncbuf != 201803L
+#     error "__cpp_lib_syncbuf should have the value 201803L in c++20"
+#   endif
+# else
+#   ifdef __cpp_lib_syncbuf
+#     error "__cpp_lib_syncbuf should not be defined when the requirement '!defined(_LIBCPP_HAS_NO_EXPERIMENTAL_SYNCSTREAM)' is not met!"
+#   endif
+# endif
+
+#elif TEST_STD_VER == 23
+
+# if !defined(_LIBCPP_HAS_NO_EXPERIMENTAL_SYNCSTREAM)
+#   ifndef __cpp_lib_syncbuf
+#     error "__cpp_lib_syncbuf should be defined in c++23"
+#   endif
+#   if __cpp_lib_syncbuf != 201803L
+#     error "__cpp_lib_syncbuf should have the value 201803L in c++23"
+#   endif
+# else
+#   ifdef __cpp_lib_syncbuf
+#     error "__cpp_lib_syncbuf should not be defined when the requirement '!defined(_LIBCPP_HAS_NO_EXPERIMENTAL_SYNCSTREAM)' is not met!"
+#   endif
+# endif
+
+#elif TEST_STD_VER > 23
+
+# if !defined(_LIBCPP_HAS_NO_EXPERIMENTAL_SYNCSTREAM)
+#   ifndef __cpp_lib_syncbuf
+#     error "__cpp_lib_syncbuf should be defined in c++26"
+#   endif
+#   if __cpp_lib_syncbuf != 201803L
+#     error "__cpp_lib_syncbuf should have the value 201803L in c++26"
+#   endif
+# else
+#   ifdef __cpp_lib_syncbuf
+#     error "__cpp_lib_syncbuf should not be defined when the requirement '!defined(_LIBCPP_HAS_NO_EXPERIMENTAL_SYNCSTREAM)' is not met!"
+#   endif
+# endif
+
+#endif // TEST_STD_VER > 23
+

diff  --git a/libcxx/test/std/language.support/support.limits/support.limits.general/version.version.compile.pass.cpp b/libcxx/test/std/language.support/support.limits/support.limits.general/version.version.compile.pass.cpp
index 3cc7ed5f6d22271..0c1b8fa997ffd85 100644
--- a/libcxx/test/std/language.support/support.limits/support.limits.general/version.version.compile.pass.cpp
+++ b/libcxx/test/std/language.support/support.limits/support.limits.general/version.version.compile.pass.cpp
@@ -3822,16 +3822,16 @@
 #   error "__cpp_lib_submdspan should not be defined before c++26"
 # endif
 
-# if !defined(_LIBCPP_VERSION)
+# if !defined(_LIBCPP_HAS_NO_EXPERIMENTAL_SYNCSTREAM)
 #   ifndef __cpp_lib_syncbuf
 #     error "__cpp_lib_syncbuf should be defined in c++20"
 #   endif
 #   if __cpp_lib_syncbuf != 201803L
 #     error "__cpp_lib_syncbuf should have the value 201803L in c++20"
 #   endif
-# else // _LIBCPP_VERSION
+# else
 #   ifdef __cpp_lib_syncbuf
-#     error "__cpp_lib_syncbuf should not be defined because it is unimplemented in libc++!"
+#     error "__cpp_lib_syncbuf should not be defined when the requirement '!defined(_LIBCPP_HAS_NO_EXPERIMENTAL_SYNCSTREAM)' is not met!"
 #   endif
 # endif
 
@@ -5294,16 +5294,16 @@
 #   error "__cpp_lib_submdspan should not be defined before c++26"
 # endif
 
-# if !defined(_LIBCPP_VERSION)
+# if !defined(_LIBCPP_HAS_NO_EXPERIMENTAL_SYNCSTREAM)
 #   ifndef __cpp_lib_syncbuf
 #     error "__cpp_lib_syncbuf should be defined in c++23"
 #   endif
 #   if __cpp_lib_syncbuf != 201803L
 #     error "__cpp_lib_syncbuf should have the value 201803L in c++23"
 #   endif
-# else // _LIBCPP_VERSION
+# else
 #   ifdef __cpp_lib_syncbuf
-#     error "__cpp_lib_syncbuf should not be defined because it is unimplemented in libc++!"
+#     error "__cpp_lib_syncbuf should not be defined when the requirement '!defined(_LIBCPP_HAS_NO_EXPERIMENTAL_SYNCSTREAM)' is not met!"
 #   endif
 # endif
 
@@ -6868,16 +6868,16 @@
 #   endif
 # endif
 
-# if !defined(_LIBCPP_VERSION)
+# if !defined(_LIBCPP_HAS_NO_EXPERIMENTAL_SYNCSTREAM)
 #   ifndef __cpp_lib_syncbuf
 #     error "__cpp_lib_syncbuf should be defined in c++26"
 #   endif
 #   if __cpp_lib_syncbuf != 201803L
 #     error "__cpp_lib_syncbuf should have the value 201803L in c++26"
 #   endif
-# else // _LIBCPP_VERSION
+# else
 #   ifdef __cpp_lib_syncbuf
-#     error "__cpp_lib_syncbuf should not be defined because it is unimplemented in libc++!"
+#     error "__cpp_lib_syncbuf should not be defined when the requirement '!defined(_LIBCPP_HAS_NO_EXPERIMENTAL_SYNCSTREAM)' is not met!"
 #   endif
 # endif
 

diff  --git a/libcxx/test/support/test_allocator.h b/libcxx/test/support/test_allocator.h
index 3bde73183ab6e39..8ae04413f8a93c8 100644
--- a/libcxx/test/support/test_allocator.h
+++ b/libcxx/test/support/test_allocator.h
@@ -29,8 +29,9 @@ TEST_CONSTEXPR_CXX20 inline typename std::allocator_traits<Alloc>::size_type all
 struct test_allocator_statistics {
   int time_to_throw = 0;
   int throw_after = INT_MAX;
-  int count = 0;
-  int alloc_count = 0;
+  int count           = 0; // the number of active instances
+  int alloc_count     = 0; // the number of allocations not deallocating
+  int allocated_size  = 0; // the size of allocated elements
   int construct_count = 0; // the number of times that ::construct was called
   int destroy_count = 0; // the number of times that ::destroy was called
   int copied = 0;
@@ -42,6 +43,7 @@ struct test_allocator_statistics {
     count = 0;
     time_to_throw = 0;
     alloc_count = 0;
+    allocated_size  = 0;
     construct_count = 0;
     destroy_count = 0;
     throw_after = INT_MAX;
@@ -155,14 +157,17 @@ class test_allocator {
         TEST_THROW(std::bad_alloc());
       ++stats_->time_to_throw;
       ++stats_->alloc_count;
+      stats_->allocated_size += n;
     }
     return std::allocator<value_type>().allocate(n);
   }
 
   TEST_CONSTEXPR_CXX14 void deallocate(pointer p, size_type s) {
     assert(data_ != test_alloc_base::destructed_value);
-    if (stats_ != nullptr)
+    if (stats_ != nullptr) {
       --stats_->alloc_count;
+      stats_->allocated_size -= s;
+    }
     std::allocator<value_type>().deallocate(p, s);
   }
 
@@ -275,6 +280,7 @@ class other_allocator {
   }
 
   TEST_CONSTEXPR_CXX14 friend bool operator!=(const other_allocator& x, const other_allocator& y) { return !(x == y); }
+  TEST_CONSTEXPR int get_data() const { return data_; }
 
   typedef std::true_type propagate_on_container_copy_assignment;
   typedef std::true_type propagate_on_container_move_assignment;

diff  --git a/libcxx/utils/generate_feature_test_macro_components.py b/libcxx/utils/generate_feature_test_macro_components.py
index 774fb6155398f90..98cf995ec223651 100755
--- a/libcxx/utils/generate_feature_test_macro_components.py
+++ b/libcxx/utils/generate_feature_test_macro_components.py
@@ -1023,7 +1023,8 @@ def add_version_header(tc):
             "name": "__cpp_lib_syncbuf",
             "values": {"c++20": 201803},
             "headers": ["syncstream"],
-            "unimplemented": True,
+            "test_suite_guard": "!defined(_LIBCPP_HAS_NO_EXPERIMENTAL_SYNCSTREAM)",
+            "libcxx_guard": "!defined(_LIBCPP_HAS_NO_EXPERIMENTAL_SYNCSTREAM)",
         },
         {
             "name": "__cpp_lib_text_encoding",
@@ -1180,6 +1181,7 @@ def add_version_header(tc):
     "semaphore": ["UNSUPPORTED: no-threads"],
     "shared_mutex": ["UNSUPPORTED: no-threads"],
     "sstream": ["UNSUPPORTED: no-localization"],
+    "syncstream": ["UNSUPPORTED: no-localization"],
     "stdatomic.h": ["UNSUPPORTED: no-threads"],
     "stop_token": ["UNSUPPORTED: no-threads"],
     "thread": ["UNSUPPORTED: no-threads"],

diff  --git a/libcxx/utils/libcxx/header_information.py b/libcxx/utils/libcxx/header_information.py
index db6f3246f6ba712..5fdb027e22b114f 100644
--- a/libcxx/utils/libcxx/header_information.py
+++ b/libcxx/utils/libcxx/header_information.py
@@ -30,6 +30,7 @@
     "sstream": "!defined(_LIBCPP_HAS_NO_LOCALIZATION)",
     "streambuf": "!defined(_LIBCPP_HAS_NO_LOCALIZATION)",
     "strstream": "!defined(_LIBCPP_HAS_NO_LOCALIZATION)",
+    "syncstream": "!defined(_LIBCPP_HAS_NO_LOCALIZATION)",
 
     # headers with #error directives
     "barrier": "!defined(_LIBCPP_HAS_NO_THREADS)",
@@ -92,6 +93,7 @@
     "stop_token": "// UNSUPPORTED: no-threads, c++03, c++11, c++14, c++17",
     "streambuf": "// UNSUPPORTED: no-localization",
     "strstream": "// UNSUPPORTED: no-localization",
+    "syncstream": "// UNSUPPORTED: no-localization",
     "thread": "// UNSUPPORTED: no-threads, c++03",
     "wchar.h": "// UNSUPPORTED: no-wide-characters",
     "wctype.h": "// UNSUPPORTED: no-wide-characters",
@@ -127,7 +129,7 @@
     "stack": ["compare", "initializer_list"],
     "string_view": ["compare"],
     "string": ["compare", "initializer_list"],
-    # TODO "syncstream": ["ostream"],
+    "syncstream": ["ostream"],
     "system_error": ["compare"],
     "tgmath.h": ["cmath", "complex"],
     "thread": ["compare"],
@@ -156,7 +158,6 @@
     "spanstream",
     "stacktrace",
     "stdfloat",
-    "syncstream",
     "text_encoding",
 ]
 

diff  --git a/libcxx/utils/libcxx/test/params.py b/libcxx/utils/libcxx/test/params.py
index 2ead641354e585d..cf190cf40d1cbb0 100644
--- a/libcxx/utils/libcxx/test/params.py
+++ b/libcxx/utils/libcxx/test/params.py
@@ -267,6 +267,7 @@ def getStdFlag(cfg, std):
             AddFeature("libcpp-has-no-incomplete-pstl"),
             AddFeature("libcpp-has-no-experimental-stop_token"),
             AddFeature("libcpp-has-no-incomplete-tzdb"),
+            AddFeature("libcpp-has-no-experimental-syncstream"),
         ],
     ),
     Parameter(


        


More information about the libcxx-commits mailing list