diff --git a/compiler/rustc_passes/src/check_attr.rs b/compiler/rustc_passes/src/check_attr.rs
index 1bb6b899875ff..3fea75954b958 100644
--- a/compiler/rustc_passes/src/check_attr.rs
+++ b/compiler/rustc_passes/src/check_attr.rs
@@ -717,6 +717,42 @@ impl CheckAttrVisitor<'tcx> {
         true
     }
 
+    /// Checks that `doc(test(...))` attribute contains only valid attributes. Returns `true` if
+    /// valid.
+    fn check_test_attr(&self, meta: &NestedMetaItem, hir_id: HirId) -> bool {
+        let mut is_valid = true;
+        if let Some(metas) = meta.meta_item_list() {
+            for i_meta in metas {
+                match i_meta.name_or_empty() {
+                    sym::attr | sym::no_crate_inject => {}
+                    _ => {
+                        self.tcx.struct_span_lint_hir(
+                            INVALID_DOC_ATTRIBUTES,
+                            hir_id,
+                            i_meta.span(),
+                            |lint| {
+                                lint.build(&format!(
+                                    "unknown `doc(test)` attribute `{}`",
+                                    rustc_ast_pretty::pprust::path_to_string(
+                                        &i_meta.meta_item().unwrap().path
+                                    ),
+                                ))
+                                .emit();
+                            },
+                        );
+                        is_valid = false;
+                    }
+                }
+            }
+        } else {
+            self.tcx.struct_span_lint_hir(INVALID_DOC_ATTRIBUTES, hir_id, meta.span(), |lint| {
+                lint.build("`#[doc(test(...)]` takes a list of attributes").emit();
+            });
+            is_valid = false;
+        }
+        is_valid
+    }
+
     /// Runs various checks on `#[doc]` attributes. Returns `true` if valid.
     ///
     /// `specified_inline` should be initialized to `None` and kept for the scope
@@ -793,8 +829,13 @@ impl CheckAttrVisitor<'tcx> {
                         | sym::no_inline
                         | sym::notable_trait
                         | sym::passes
-                        | sym::plugins
-                        | sym::test => {}
+                        | sym::plugins => {}
+
+                        sym::test => {
+                            if !self.check_test_attr(&meta, hir_id) {
+                                is_valid = false;
+                            }
+                        }
 
                         sym::primitive => {
                             if !self.tcx.features().doc_primitive {
diff --git a/src/test/rustdoc-ui/doc-test-attr-pass.rs b/src/test/rustdoc-ui/doc-test-attr-pass.rs
new file mode 100644
index 0000000000000..12608f2445075
--- /dev/null
+++ b/src/test/rustdoc-ui/doc-test-attr-pass.rs
@@ -0,0 +1,8 @@
+// check-pass
+
+#![crate_type = "lib"]
+#![deny(invalid_doc_attributes)]
+#![doc(test(no_crate_inject))]
+#![doc(test(attr(deny(warnings))))]
+
+pub fn foo() {}
diff --git a/src/test/rustdoc-ui/doc-test-attr.rs b/src/test/rustdoc-ui/doc-test-attr.rs
new file mode 100644
index 0000000000000..46178ad865a4c
--- /dev/null
+++ b/src/test/rustdoc-ui/doc-test-attr.rs
@@ -0,0 +1,14 @@
+#![crate_type = "lib"]
+#![deny(invalid_doc_attributes)]
+
+#![doc(test)]
+//~^ ERROR `#[doc(test(...)]` takes a list of attributes
+//~^^ WARN this was previously accepted by the compiler
+#![doc(test = "hello")]
+//~^ ERROR `#[doc(test(...)]` takes a list of attributes
+//~^^ WARN this was previously accepted by the compiler
+#![doc(test(a))]
+//~^ ERROR unknown `doc(test)` attribute `a`
+//~^^ WARN this was previously accepted by the compiler
+
+pub fn foo() {}
diff --git a/src/test/rustdoc-ui/doc-test-attr.stderr b/src/test/rustdoc-ui/doc-test-attr.stderr
new file mode 100644
index 0000000000000..7f5e2d6bc70d9
--- /dev/null
+++ b/src/test/rustdoc-ui/doc-test-attr.stderr
@@ -0,0 +1,34 @@
+error: `#[doc(test(...)]` takes a list of attributes
+  --> $DIR/doc-test-attr.rs:4:8
+   |
+LL | #![doc(test)]
+   |        ^^^^
+   |
+note: the lint level is defined here
+  --> $DIR/doc-test-attr.rs:2:9
+   |
+LL | #![deny(invalid_doc_attributes)]
+   |         ^^^^^^^^^^^^^^^^^^^^^^
+   = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
+   = note: for more information, see issue #82730 <https://github.com/rust-lang/rust/issues/82730>
+
+error: `#[doc(test(...)]` takes a list of attributes
+  --> $DIR/doc-test-attr.rs:7:8
+   |
+LL | #![doc(test = "hello")]
+   |        ^^^^^^^^^^^^^^
+   |
+   = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
+   = note: for more information, see issue #82730 <https://github.com/rust-lang/rust/issues/82730>
+
+error: unknown `doc(test)` attribute `a`
+  --> $DIR/doc-test-attr.rs:10:13
+   |
+LL | #![doc(test(a))]
+   |             ^
+   |
+   = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
+   = note: for more information, see issue #82730 <https://github.com/rust-lang/rust/issues/82730>
+
+error: aborting due to 3 previous errors
+
diff --git a/src/test/ui/rustdoc/doc-test-attr-pass.rs b/src/test/ui/rustdoc/doc-test-attr-pass.rs
new file mode 100644
index 0000000000000..7884addd15fe7
--- /dev/null
+++ b/src/test/ui/rustdoc/doc-test-attr-pass.rs
@@ -0,0 +1,9 @@
+// check-pass
+
+#![crate_type = "lib"]
+#![deny(invalid_doc_attributes)]
+#![doc(test(no_crate_inject))]
+#![doc(test(attr(deny(warnings))))]
+#![doc(test())]
+
+pub fn foo() {}
diff --git a/src/test/ui/rustdoc/doc-test-attr.rs b/src/test/ui/rustdoc/doc-test-attr.rs
new file mode 100644
index 0000000000000..46178ad865a4c
--- /dev/null
+++ b/src/test/ui/rustdoc/doc-test-attr.rs
@@ -0,0 +1,14 @@
+#![crate_type = "lib"]
+#![deny(invalid_doc_attributes)]
+
+#![doc(test)]
+//~^ ERROR `#[doc(test(...)]` takes a list of attributes
+//~^^ WARN this was previously accepted by the compiler
+#![doc(test = "hello")]
+//~^ ERROR `#[doc(test(...)]` takes a list of attributes
+//~^^ WARN this was previously accepted by the compiler
+#![doc(test(a))]
+//~^ ERROR unknown `doc(test)` attribute `a`
+//~^^ WARN this was previously accepted by the compiler
+
+pub fn foo() {}
diff --git a/src/test/ui/rustdoc/doc-test-attr.stderr b/src/test/ui/rustdoc/doc-test-attr.stderr
new file mode 100644
index 0000000000000..7f5e2d6bc70d9
--- /dev/null
+++ b/src/test/ui/rustdoc/doc-test-attr.stderr
@@ -0,0 +1,34 @@
+error: `#[doc(test(...)]` takes a list of attributes
+  --> $DIR/doc-test-attr.rs:4:8
+   |
+LL | #![doc(test)]
+   |        ^^^^
+   |
+note: the lint level is defined here
+  --> $DIR/doc-test-attr.rs:2:9
+   |
+LL | #![deny(invalid_doc_attributes)]
+   |         ^^^^^^^^^^^^^^^^^^^^^^
+   = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
+   = note: for more information, see issue #82730 <https://github.com/rust-lang/rust/issues/82730>
+
+error: `#[doc(test(...)]` takes a list of attributes
+  --> $DIR/doc-test-attr.rs:7:8
+   |
+LL | #![doc(test = "hello")]
+   |        ^^^^^^^^^^^^^^
+   |
+   = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
+   = note: for more information, see issue #82730 <https://github.com/rust-lang/rust/issues/82730>
+
+error: unknown `doc(test)` attribute `a`
+  --> $DIR/doc-test-attr.rs:10:13
+   |
+LL | #![doc(test(a))]
+   |             ^
+   |
+   = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
+   = note: for more information, see issue #82730 <https://github.com/rust-lang/rust/issues/82730>
+
+error: aborting due to 3 previous errors
+