build_dir: str
        timeout: int
        filter_glob: str
+       filter: str
+       filter_action: Optional[str]
        kernel_args: Optional[List[str]]
        run_isolated: Optional[str]
+       list_tests: bool
+       list_tests_attr: bool
 
 @dataclass
 class KunitRequest(KunitExecRequest, KunitBuildRequest):
 
 def _list_tests(linux: kunit_kernel.LinuxSourceTree, request: KunitExecRequest) -> List[str]:
        args = ['kunit.action=list']
+
+       if request.kernel_args:
+               args.extend(request.kernel_args)
+
+       output = linux.run_kernel(args=args,
+                          timeout=request.timeout,
+                          filter_glob=request.filter_glob,
+                          filter=request.filter,
+                          filter_action=request.filter_action,
+                          build_dir=request.build_dir)
+       lines = kunit_parser.extract_tap_lines(output)
+       # Hack! Drop the dummy TAP version header that the executor prints out.
+       lines.pop()
+
+       # Filter out any extraneous non-test output that might have gotten mixed in.
+       return [l for l in output if re.match(r'^[^\s.]+\.[^\s.]+$', l)]
+
+def _list_tests_attr(linux: kunit_kernel.LinuxSourceTree, request: KunitExecRequest) -> Iterable[str]:
+       args = ['kunit.action=list_attr']
+
        if request.kernel_args:
                args.extend(request.kernel_args)
 
        output = linux.run_kernel(args=args,
                           timeout=request.timeout,
                           filter_glob=request.filter_glob,
+                          filter=request.filter,
+                          filter_action=request.filter_action,
                           build_dir=request.build_dir)
        lines = kunit_parser.extract_tap_lines(output)
        # Hack! Drop the dummy TAP version header that the executor prints out.
        lines.pop()
 
        # Filter out any extraneous non-test output that might have gotten mixed in.
-       return [l for l in lines if re.match(r'^[^\s.]+\.[^\s.]+$', l)]
+       return lines
 
 def _suites_from_test_list(tests: List[str]) -> List[str]:
        """Extracts all the suites from an ordered list of tests."""
                        suites.append(suite)
        return suites
 
-
-
 def exec_tests(linux: kunit_kernel.LinuxSourceTree, request: KunitExecRequest) -> KunitResult:
        filter_globs = [request.filter_glob]
+       if request.list_tests:
+               output = _list_tests(linux, request)
+               for line in output:
+                       print(line.rstrip())
+               return KunitResult(status=KunitStatus.SUCCESS, elapsed_time=0.0)
+       if request.list_tests_attr:
+               attr_output = _list_tests_attr(linux, request)
+               for line in attr_output:
+                       print(line.rstrip())
+               return KunitResult(status=KunitStatus.SUCCESS, elapsed_time=0.0)
        if request.run_isolated:
                tests = _list_tests(linux, request)
                if request.run_isolated == 'test':
                        args=request.kernel_args,
                        timeout=request.timeout,
                        filter_glob=filter_glob,
+                       filter=request.filter,
+                       filter_action=request.filter_action,
                        build_dir=request.build_dir)
 
                _, test_result = parse_tests(request, metadata, run_result)
                            nargs='?',
                            default='',
                            metavar='filter_glob')
+       parser.add_argument('--filter',
+                           help='Filter KUnit tests with attributes, '
+                           'e.g. module=example or speed>slow',
+                           type=str,
+                               default='')
+       parser.add_argument('--filter_action',
+                           help='If set to skip, filtered tests will be skipped, '
+                               'e.g. --filter_action=skip. Otherwise they will not run.',
+                           type=str,
+                               choices=['skip'])
        parser.add_argument('--kernel_args',
                            help='Kernel command-line parameters. Maybe be repeated',
                             action='append', metavar='')
                            'what ran before it.',
                            type=str,
                            choices=['suite', 'test'])
+       parser.add_argument('--list_tests', help='If set, list all tests that will be '
+                           'run.',
+                           action='store_true')
+       parser.add_argument('--list_tests_attr', help='If set, list all tests and test '
+                           'attributes.',
+                           action='store_true')
 
 def add_parse_opts(parser: argparse.ArgumentParser) -> None:
        parser.add_argument('--raw_output', help='If set don\'t parse output from kernel. '
                                        json=cli_args.json,
                                        timeout=cli_args.timeout,
                                        filter_glob=cli_args.filter_glob,
+                                       filter=cli_args.filter,
+                                       filter_action=cli_args.filter_action,
                                        kernel_args=cli_args.kernel_args,
-                                       run_isolated=cli_args.run_isolated)
+                                       run_isolated=cli_args.run_isolated,
+                                       list_tests=cli_args.list_tests,
+                                       list_tests_attr=cli_args.list_tests_attr)
        result = run_tests(linux, request)
        if result.status != KunitStatus.SUCCESS:
                sys.exit(1)
                                        json=cli_args.json,
                                        timeout=cli_args.timeout,
                                        filter_glob=cli_args.filter_glob,
+                                       filter=cli_args.filter,
+                                       filter_action=cli_args.filter_action,
                                        kernel_args=cli_args.kernel_args,
-                                       run_isolated=cli_args.run_isolated)
+                                       run_isolated=cli_args.run_isolated,
+                                       list_tests=cli_args.list_tests,
+                                       list_tests_attr=cli_args.list_tests_attr)
        result = exec_tests(linux, exec_request)
        stdout.print_with_timestamp((
                'Elapsed time: %.3fs\n') % (result.elapsed_time))
 
                self.assertEqual(self.linux_source_mock.build_reconfig.call_count, 0)
                self.assertEqual(self.linux_source_mock.run_kernel.call_count, 1)
                self.linux_source_mock.run_kernel.assert_called_once_with(
-                       args=None, build_dir='.kunit', filter_glob='', timeout=300)
+                       args=None, build_dir='.kunit', filter_glob='', filter='', filter_action=None, timeout=300)
                self.print_mock.assert_any_call(StrContains('Testing complete.'))
 
        def test_run_passes_args_pass(self):
                self.assertEqual(self.linux_source_mock.build_reconfig.call_count, 1)
                self.assertEqual(self.linux_source_mock.run_kernel.call_count, 1)
                self.linux_source_mock.run_kernel.assert_called_once_with(
-                       args=None, build_dir='.kunit', filter_glob='', timeout=300)
+                       args=None, build_dir='.kunit', filter_glob='', filter='', filter_action=None, timeout=300)
                self.print_mock.assert_any_call(StrContains('Testing complete.'))
 
        def test_exec_passes_args_fail(self):
                        kunit.main(['run'])
                self.assertEqual(e.exception.code, 1)
                self.linux_source_mock.run_kernel.assert_called_once_with(
-                       args=None, build_dir='.kunit', filter_glob='', timeout=300)
+                       args=None, build_dir='.kunit', filter_glob='', filter='', filter_action=None, timeout=300)
                self.print_mock.assert_any_call(StrContains(' 0 tests run!'))
 
        def test_exec_raw_output(self):
                self.linux_source_mock.run_kernel = mock.Mock(return_value=[])
                kunit.main(['run', '--raw_output', 'filter_glob'])
                self.linux_source_mock.run_kernel.assert_called_once_with(
-                       args=None, build_dir='.kunit', filter_glob='filter_glob', timeout=300)
+                       args=None, build_dir='.kunit', filter_glob='filter_glob', filter='', filter_action=None, timeout=300)
 
        def test_exec_timeout(self):
                timeout = 3453
                kunit.main(['exec', '--timeout', str(timeout)])
                self.linux_source_mock.run_kernel.assert_called_once_with(
-                       args=None, build_dir='.kunit', filter_glob='', timeout=timeout)
+                       args=None, build_dir='.kunit', filter_glob='', filter='', filter_action=None, timeout=timeout)
                self.print_mock.assert_any_call(StrContains('Testing complete.'))
 
        def test_run_timeout(self):
                kunit.main(['run', '--timeout', str(timeout)])
                self.assertEqual(self.linux_source_mock.build_reconfig.call_count, 1)
                self.linux_source_mock.run_kernel.assert_called_once_with(
-                       args=None, build_dir='.kunit', filter_glob='', timeout=timeout)
+                       args=None, build_dir='.kunit', filter_glob='', filter='', filter_action=None, timeout=timeout)
                self.print_mock.assert_any_call(StrContains('Testing complete.'))
 
        def test_run_builddir(self):
                kunit.main(['run', '--build_dir=.kunit'])
                self.assertEqual(self.linux_source_mock.build_reconfig.call_count, 1)
                self.linux_source_mock.run_kernel.assert_called_once_with(
-                       args=None, build_dir=build_dir, filter_glob='', timeout=300)
+                       args=None, build_dir=build_dir, filter_glob='', filter='', filter_action=None, timeout=300)
                self.print_mock.assert_any_call(StrContains('Testing complete.'))
 
        def test_config_builddir(self):
                build_dir = '.kunit'
                kunit.main(['exec', '--build_dir', build_dir])
                self.linux_source_mock.run_kernel.assert_called_once_with(
-                       args=None, build_dir=build_dir, filter_glob='', timeout=300)
+                       args=None, build_dir=build_dir, filter_glob='', filter='', filter_action=None, timeout=300)
                self.print_mock.assert_any_call(StrContains('Testing complete.'))
 
        def test_run_kunitconfig(self):
                kunit.main(['run', '--kernel_args=a=1', '--kernel_args=b=2'])
                self.assertEqual(self.linux_source_mock.build_reconfig.call_count, 1)
                self.linux_source_mock.run_kernel.assert_called_once_with(
-                     args=['a=1','b=2'], build_dir='.kunit', filter_glob='', timeout=300)
+                     args=['a=1','b=2'], build_dir='.kunit', filter_glob='', filter='', filter_action=None, timeout=300)
                self.print_mock.assert_any_call(StrContains('Testing complete.'))
 
        def test_list_tests(self):
                self.linux_source_mock.run_kernel.return_value = ['TAP version 14', 'init: random output'] + want
 
                got = kunit._list_tests(self.linux_source_mock,
-                                    kunit.KunitExecRequest(None, None, '.kunit', 300, 'suite*', None, 'suite'))
-
+                                    kunit.KunitExecRequest(None, None, '.kunit', 300, 'suite*', '', None, None, 'suite', False, False))
                self.assertEqual(got, want)
                # Should respect the user's filter glob when listing tests.
                self.linux_source_mock.run_kernel.assert_called_once_with(
-                       args=['kunit.action=list'], build_dir='.kunit', filter_glob='suite*', timeout=300)
-
+                       args=['kunit.action=list'], build_dir='.kunit', filter_glob='suite*', filter='', filter_action=None, timeout=300)
 
        @mock.patch.object(kunit, '_list_tests')
        def test_run_isolated_by_suite(self, mock_tests):
 
                # Should respect the user's filter glob when listing tests.
                mock_tests.assert_called_once_with(mock.ANY,
-                                    kunit.KunitExecRequest(None, None, '.kunit', 300, 'suite*.test*', None, 'suite'))
+                                    kunit.KunitExecRequest(None, None, '.kunit', 300, 'suite*.test*', '', None, None, 'suite', False, False))
                self.linux_source_mock.run_kernel.assert_has_calls([
-                       mock.call(args=None, build_dir='.kunit', filter_glob='suite.test*', timeout=300),
-                       mock.call(args=None, build_dir='.kunit', filter_glob='suite2.test*', timeout=300),
+                       mock.call(args=None, build_dir='.kunit', filter_glob='suite.test*', filter='', filter_action=None, timeout=300),
+                       mock.call(args=None, build_dir='.kunit', filter_glob='suite2.test*', filter='', filter_action=None, timeout=300),
                ])
 
        @mock.patch.object(kunit, '_list_tests')
 
                # Should respect the user's filter glob when listing tests.
                mock_tests.assert_called_once_with(mock.ANY,
-                                    kunit.KunitExecRequest(None, None, '.kunit', 300, 'suite*', None, 'test'))
+                                    kunit.KunitExecRequest(None, None, '.kunit', 300, 'suite*', '', None, None, 'test', False, False))
                self.linux_source_mock.run_kernel.assert_has_calls([
-                       mock.call(args=None, build_dir='.kunit', filter_glob='suite.test1', timeout=300),
-                       mock.call(args=None, build_dir='.kunit', filter_glob='suite.test2', timeout=300),
-                       mock.call(args=None, build_dir='.kunit', filter_glob='suite2.test1', timeout=300),
+                       mock.call(args=None, build_dir='.kunit', filter_glob='suite.test1', filter='', filter_action=None, timeout=300),
+                       mock.call(args=None, build_dir='.kunit', filter_glob='suite.test2', filter='', filter_action=None, timeout=300),
+                       mock.call(args=None, build_dir='.kunit', filter_glob='suite2.test1', filter='', filter_action=None, timeout=300),
                ])
 
-
 if __name__ == '__main__':
        unittest.main()