Path: blob/main/singlestoredb/docstring/tests/test_google.py
469 views
"""Tests for Google-style docstring routines."""1import typing as T23import pytest45import singlestoredb.docstring.google as google6from singlestoredb.docstring.common import ParseError7from singlestoredb.docstring.common import RenderingStyle8from singlestoredb.docstring.google import compose9from singlestoredb.docstring.google import GoogleParser10from singlestoredb.docstring.google import parse11from singlestoredb.docstring.google import Section12from singlestoredb.docstring.google import SectionType131415def test_google_parser_unknown_section() -> None:16"""Test parsing an unknown section with default GoogleParser17configuration.18"""19parser = GoogleParser()20docstring = parser.parse(21"""22Unknown:23spam: a24""",25)26assert docstring.short_description == 'Unknown:'27assert docstring.long_description == 'spam: a'28assert len(docstring.meta) == 0293031def test_google_parser_multi_line_parameter_type() -> None:32"""Test parsing a multi-line parameter type with default GoogleParser"""33parser = GoogleParser()34docstring = parser.parse(35"""Description of the function.3637Args:38output_type (Literal["searchResults", "sourcedAnswer",39"structured"]): The type of output.40This can be one of the following:41- "searchResults": Represents the search results.42- "sourcedAnswer": Represents a sourced answer.43- "structured": Represents a structured output format.4445Returns:46bool: Indicates success or failure.4748""",49)50assert docstring.params[0].arg_name == 'output_type'515253def test_google_parser_custom_sections() -> None:54"""Test parsing an unknown section with custom GoogleParser55configuration.56"""57parser = GoogleParser(58[59Section('DESCRIPTION', 'desc', SectionType.SINGULAR),60Section('ARGUMENTS', 'param', SectionType.MULTIPLE),61Section('ATTRIBUTES', 'attribute', SectionType.MULTIPLE),62Section('EXAMPLES', 'examples', SectionType.SINGULAR),63],64title_colon=False,65)66docstring = parser.parse(67"""68DESCRIPTION69This is the description.7071ARGUMENTS72arg1: first arg73arg2: second arg7475ATTRIBUTES76attr1: first attribute77attr2: second attribute7879EXAMPLES80Many examples81More examples82""",83)8485assert docstring.short_description is None86assert docstring.long_description is None87assert len(docstring.meta) == 688assert docstring.meta[0].args == ['desc']89assert docstring.meta[0].description == 'This is the description.'90assert docstring.meta[1].args == ['param', 'arg1']91assert docstring.meta[1].description == 'first arg'92assert docstring.meta[2].args == ['param', 'arg2']93assert docstring.meta[2].description == 'second arg'94assert docstring.meta[3].args == ['attribute', 'attr1']95assert docstring.meta[3].description == 'first attribute'96assert docstring.meta[4].args == ['attribute', 'attr2']97assert docstring.meta[4].description == 'second attribute'98assert docstring.meta[5].args == ['examples']99assert docstring.meta[5].description == 'Many examples\nMore examples'100101102def test_google_parser_custom_sections_after() -> None:103"""Test parsing an unknown section with custom GoogleParser configuration104that was set at a runtime.105"""106parser = GoogleParser(title_colon=False)107parser.add_section(Section('Note', 'note', SectionType.SINGULAR))108docstring = parser.parse(109"""110short description111112Note:113a note114""",115)116assert docstring.short_description == 'short description'117assert docstring.long_description == 'Note:\n a note'118119docstring = parser.parse(120"""121short description122123Note a note124""",125)126assert docstring.short_description == 'short description'127assert docstring.long_description == 'Note a note'128129docstring = parser.parse(130"""131short description132133Note134a note135""",136)137assert len(docstring.meta) == 1138assert docstring.meta[0].args == ['note']139assert docstring.meta[0].description == 'a note'140141142@pytest.mark.parametrize(143'source, expected',144[145pytest.param(None, None, id='No __doc__'),146('', None),147('\n', None),148('Short description', 'Short description'),149('\nShort description\n', 'Short description'),150('\n Short description\n', 'Short description'),151],152)153def test_short_description(154source: T.Optional[str], expected: T.Optional[str],155) -> None:156"""Test parsing short description."""157docstring = parse(source)158assert docstring.short_description == expected159assert docstring.long_description is None160assert not docstring.meta161162163@pytest.mark.parametrize(164'source, expected_short_desc, expected_long_desc, expected_blank',165[166(167'Short description\n\nLong description',168'Short description',169'Long description',170True,171),172(173"""174Short description175176Long description177""",178'Short description',179'Long description',180True,181),182(183"""184Short description185186Long description187Second line188""",189'Short description',190'Long description\nSecond line',191True,192),193(194'Short description\nLong description',195'Short description',196'Long description',197False,198),199(200"""201Short description202Long description203""",204'Short description',205'Long description',206False,207),208(209'\nShort description\nLong description\n',210'Short description',211'Long description',212False,213),214(215"""216Short description217Long description218Second line219""",220'Short description',221'Long description\nSecond line',222False,223),224],225)226def test_long_description(227source: str,228expected_short_desc: str,229expected_long_desc: str,230expected_blank: bool,231) -> None:232"""Test parsing long description."""233docstring = parse(source)234assert docstring.short_description == expected_short_desc235assert docstring.long_description == expected_long_desc236assert docstring.blank_after_short_description == expected_blank237assert not docstring.meta238239240@pytest.mark.parametrize(241'source, expected_short_desc, expected_long_desc, '242'expected_blank_short_desc, expected_blank_long_desc',243[244(245"""246Short description247Args:248asd:249""",250'Short description',251None,252False,253False,254),255(256"""257Short description258Long description259Args:260asd:261""",262'Short description',263'Long description',264False,265False,266),267(268"""269Short description270First line271Second line272Args:273asd:274""",275'Short description',276'First line\n Second line',277False,278False,279),280(281"""282Short description283284First line285Second line286Args:287asd:288""",289'Short description',290'First line\n Second line',291True,292False,293),294(295"""296Short description297298First line299Second line300301Args:302asd:303""",304'Short description',305'First line\n Second line',306True,307True,308),309(310"""311Args:312asd:313""",314None,315None,316False,317False,318),319],320)321def test_meta_newlines(322source: str,323expected_short_desc: T.Optional[str],324expected_long_desc: T.Optional[str],325expected_blank_short_desc: bool,326expected_blank_long_desc: bool,327) -> None:328"""Test parsing newlines around description sections."""329docstring = parse(source)330assert docstring.short_description == expected_short_desc331assert docstring.long_description == expected_long_desc332assert docstring.blank_after_short_description == expected_blank_short_desc333assert docstring.blank_after_long_description == expected_blank_long_desc334assert len(docstring.meta) == 1335336337def test_meta_with_multiline_description() -> None:338"""Test parsing multiline meta documentation."""339docstring = parse(340"""341Short description342343Args:344spam: asd345134623473348""",349)350assert docstring.short_description == 'Short description'351assert len(docstring.meta) == 1352assert docstring.meta[0].args == ['param', 'spam']353assert isinstance(docstring.meta[0], google.DocstringParam)354assert docstring.meta[0].arg_name == 'spam'355assert docstring.meta[0].description == 'asd\n1\n 2\n3'356357358def test_default_args() -> None:359"""Test parsing default arguments."""360docstring = parse(361"""A sample function362363A function the demonstrates docstrings364365Args:366arg1 (int): The firsty arg367arg2 (str): The second arg368arg3 (float, optional): The third arg. Defaults to 1.0.369arg4 (Optional[Dict[str, Any]], optional): The last arg. Defaults to None.370arg5 (str, optional): The fifth arg. Defaults to DEFAULT_ARG5.371372Returns:373Mapping[str, Any]: The args packed in a mapping374""",375)376assert docstring is not None377assert len(docstring.params) == 5378379arg4 = docstring.params[3]380assert arg4.arg_name == 'arg4'381assert arg4.is_optional382assert arg4.type_name == 'Optional[Dict[str, Any]]'383assert arg4.default == 'None'384assert arg4.description == 'The last arg. Defaults to None.'385386387def test_multiple_meta() -> None:388"""Test parsing multiple meta."""389docstring = parse(390"""391Short description392393Args:394spam: asd395139623973398399Raises:400bla: herp401yay: derp402""",403)404assert docstring.short_description == 'Short description'405assert len(docstring.meta) == 3406assert docstring.meta[0].args == ['param', 'spam']407assert isinstance(docstring.meta[0], google.DocstringParam)408assert docstring.meta[0].arg_name == 'spam'409assert docstring.meta[0].description == 'asd\n1\n 2\n3'410assert docstring.meta[1].args == ['raises', 'bla']411assert isinstance(docstring.meta[1], google.DocstringRaises)412assert docstring.meta[1].type_name == 'bla'413assert docstring.meta[1].description == 'herp'414assert isinstance(docstring.meta[2], google.DocstringRaises)415assert docstring.meta[2].args == ['raises', 'yay']416assert docstring.meta[2].type_name == 'yay'417assert docstring.meta[2].description == 'derp'418419420def test_params() -> None:421"""Test parsing params."""422docstring = parse('Short description')423assert len(docstring.params) == 0424425docstring = parse(426"""427Short description428429Args:430name: description 1431priority (int): description 2432sender (str?): description 3433ratio (Optional[float], optional): description 4434""",435)436assert len(docstring.params) == 4437assert docstring.params[0].arg_name == 'name'438assert docstring.params[0].type_name is None439assert docstring.params[0].description == 'description 1'440assert not docstring.params[0].is_optional441assert docstring.params[1].arg_name == 'priority'442assert docstring.params[1].type_name == 'int'443assert docstring.params[1].description == 'description 2'444assert not docstring.params[1].is_optional445assert docstring.params[2].arg_name == 'sender'446assert docstring.params[2].type_name == 'str'447assert docstring.params[2].description == 'description 3'448assert docstring.params[2].is_optional449assert docstring.params[3].arg_name == 'ratio'450assert docstring.params[3].type_name == 'Optional[float]'451assert docstring.params[3].description == 'description 4'452assert docstring.params[3].is_optional453454docstring = parse(455"""456Short description457458Args:459name: description 1460with multi-line text461priority (int): description 2462""",463)464assert len(docstring.params) == 2465assert docstring.params[0].arg_name == 'name'466assert docstring.params[0].type_name is None467assert docstring.params[0].description == (468'description 1\nwith multi-line text'469)470assert docstring.params[1].arg_name == 'priority'471assert docstring.params[1].type_name == 'int'472assert docstring.params[1].description == 'description 2'473474475def test_attributes() -> None:476"""Test parsing attributes."""477docstring = parse('Short description')478assert len(docstring.params) == 0479480docstring = parse(481"""482Short description483484Attributes:485name: description 1486priority (int): description 2487sender (str?): description 3488ratio (Optional[float], optional): description 4489""",490)491assert len(docstring.params) == 4492assert docstring.params[0].arg_name == 'name'493assert docstring.params[0].type_name is None494assert docstring.params[0].description == 'description 1'495assert not docstring.params[0].is_optional496assert docstring.params[1].arg_name == 'priority'497assert docstring.params[1].type_name == 'int'498assert docstring.params[1].description == 'description 2'499assert not docstring.params[1].is_optional500assert docstring.params[2].arg_name == 'sender'501assert docstring.params[2].type_name == 'str'502assert docstring.params[2].description == 'description 3'503assert docstring.params[2].is_optional504assert docstring.params[3].arg_name == 'ratio'505assert docstring.params[3].type_name == 'Optional[float]'506assert docstring.params[3].description == 'description 4'507assert docstring.params[3].is_optional508509docstring = parse(510"""511Short description512513Attributes:514name: description 1515with multi-line text516priority (int): description 2517""",518)519assert len(docstring.params) == 2520assert docstring.params[0].arg_name == 'name'521assert docstring.params[0].type_name is None522assert docstring.params[0].description == (523'description 1\nwith multi-line text'524)525assert docstring.params[1].arg_name == 'priority'526assert docstring.params[1].type_name == 'int'527assert docstring.params[1].description == 'description 2'528529530def test_returns() -> None:531"""Test parsing returns."""532docstring = parse(533"""534Short description535""",536)537assert docstring.returns is None538assert docstring.many_returns is not None539assert len(docstring.many_returns) == 0540541docstring = parse(542"""543Short description544Returns:545description546""",547)548assert docstring.returns is not None549assert docstring.returns.type_name is None550assert docstring.returns.description == 'description'551assert docstring.many_returns is not None552assert len(docstring.many_returns) == 1553assert docstring.many_returns[0] == docstring.returns554555docstring = parse(556"""557Short description558Returns:559description with: a colon!560""",561)562assert docstring.returns is not None563assert docstring.returns.type_name is None564assert docstring.returns.description == 'description with: a colon!'565assert docstring.many_returns is not None566assert len(docstring.many_returns) == 1567assert docstring.many_returns[0] == docstring.returns568569docstring = parse(570"""571Short description572Returns:573int: description574""",575)576assert docstring.returns is not None577assert docstring.returns.type_name == 'int'578assert docstring.returns.description == 'description'579assert docstring.many_returns is not None580assert len(docstring.many_returns) == 1581assert docstring.many_returns[0] == docstring.returns582583docstring = parse(584"""585Returns:586Optional[Mapping[str, List[int]]]: A description: with a colon587""",588)589assert docstring.returns is not None590assert docstring.returns.type_name == 'Optional[Mapping[str, List[int]]]'591assert docstring.returns.description == 'A description: with a colon'592assert docstring.many_returns is not None593assert len(docstring.many_returns) == 1594assert docstring.many_returns[0] == docstring.returns595596docstring = parse(597"""598Short description599Yields:600int: description601""",602)603assert docstring.returns is not None604assert docstring.returns.type_name == 'int'605assert docstring.returns.description == 'description'606assert docstring.many_returns is not None607assert len(docstring.many_returns) == 1608assert docstring.many_returns[0] == docstring.returns609610docstring = parse(611"""612Short description613Returns:614int: description615with much text616617even some spacing618""",619)620assert docstring.returns is not None621assert docstring.returns.type_name == 'int'622assert docstring.returns.description == (623'description\nwith much text\n\neven some spacing'624)625assert docstring.many_returns is not None626assert len(docstring.many_returns) == 1627assert docstring.many_returns[0] == docstring.returns628629630def test_raises() -> None:631"""Test parsing raises."""632docstring = parse(633"""634Short description635""",636)637assert len(docstring.raises) == 0638639docstring = parse(640"""641Short description642Raises:643ValueError: description644""",645)646assert len(docstring.raises) == 1647assert docstring.raises[0].type_name == 'ValueError'648assert docstring.raises[0].description == 'description'649650651def test_examples() -> None:652"""Test parsing examples."""653docstring = parse(654"""655Short description656Example:657example: 1658Examples:659long example660661more here662""",663)664assert len(docstring.examples) == 2665assert docstring.examples[0].description == 'example: 1'666assert docstring.examples[1].description == 'long example\n\nmore here'667668669def test_broken_meta() -> None:670"""Test parsing broken meta."""671with pytest.raises(ParseError):672parse('Args:')673674with pytest.raises(ParseError):675parse('Args:\n herp derp')676677678def test_unknown_meta() -> None:679"""Test parsing unknown meta."""680docstring = parse(681"""Short desc682683Unknown 0:684title0: content0685686Args:687arg0: desc0688arg1: desc1689690Unknown1:691title1: content1692693Unknown2:694title2: content2695""",696)697698assert docstring.params[0].arg_name == 'arg0'699assert docstring.params[0].description == 'desc0'700assert docstring.params[1].arg_name == 'arg1'701assert docstring.params[1].description == 'desc1'702703704def test_broken_arguments() -> None:705"""Test parsing broken arguments."""706with pytest.raises(ParseError):707parse(708"""This is a test709710Args:711param - poorly formatted712""",713)714715716def test_empty_example() -> None:717"""Test parsing empty examples section."""718docstring = parse(719"""Short description720721Example:722723Raises:724IOError: some error725""",726)727728assert len(docstring.examples) == 1729assert docstring.examples[0].args == ['examples']730assert docstring.examples[0].description == ''731732733@pytest.mark.parametrize(734'source, expected',735[736('', ''),737('\n', ''),738('Short description', 'Short description'),739('\nShort description\n', 'Short description'),740('\n Short description\n', 'Short description'),741(742'Short description\n\nLong description',743'Short description\n\nLong description',744),745(746"""747Short description748749Long description750""",751'Short description\n\nLong description',752),753(754"""755Short description756757Long description758Second line759""",760'Short description\n\nLong description\nSecond line',761),762(763'Short description\nLong description',764'Short description\nLong description',765),766(767"""768Short description769Long description770""",771'Short description\nLong description',772),773(774'\nShort description\nLong description\n',775'Short description\nLong description',776),777(778"""779Short description780Long description781Second line782""",783'Short description\nLong description\nSecond line',784),785(786"""787Short description788Meta:789asd790""",791'Short description\nMeta:\n asd',792),793(794"""795Short description796Long description797Meta:798asd799""",800'Short description\nLong description\nMeta:\n asd',801),802(803"""804Short description805First line806Second line807Meta:808asd809""",810'Short description\n'811'First line\n'812' Second line\n'813'Meta:\n'814' asd',815),816(817"""818Short description819820First line821Second line822Meta:823asd824""",825'Short description\n'826'\n'827'First line\n'828' Second line\n'829'Meta:\n'830' asd',831),832(833"""834Short description835836First line837Second line838839Meta:840asd841""",842'Short description\n'843'\n'844'First line\n'845' Second line\n'846'\n'847'Meta:\n'848' asd',849),850(851"""852Short description853854Meta:855asd856185728583859""",860'Short description\n'861'\n'862'Meta:\n'863' asd\n'864' 1\n'865' 2\n'866' 3',867),868(869"""870Short description871872Meta1:873asd874187528763877Meta2:878herp879Meta3:880derp881""",882'Short description\n'883'\n'884'Meta1:\n'885' asd\n'886' 1\n'887' 2\n'888' 3\n'889'Meta2:\n'890' herp\n'891'Meta3:\n'892' derp',893),894(895"""896Short description897898Args:899name: description 1900priority (int): description 2901sender (str, optional): description 3902message (str, optional): description 4, defaults to 'hello'903multiline (str?):904long description 5,905defaults to 'bye'906""",907'Short description\n'908'\n'909'Args:\n'910' name: description 1\n'911' priority (int): description 2\n'912' sender (str?): description 3\n'913" message (str?): description 4, defaults to 'hello'\n"914' multiline (str?): long description 5,\n'915" defaults to 'bye'",916),917(918"""919Short description920Raises:921ValueError: description922""",923'Short description\nRaises:\n ValueError: description',924),925],926)927def test_compose(source: str, expected: str) -> None:928"""Test compose in default mode."""929assert compose(parse(source)) == expected930931932@pytest.mark.parametrize(933'source, expected',934[935(936"""937Short description938939Args:940name: description 1941priority (int): description 2942sender (str, optional): description 3943message (str, optional): description 4, defaults to 'hello'944multiline (str?):945long description 5,946defaults to 'bye'947""",948'Short description\n'949'\n'950'Args:\n'951' name: description 1\n'952' priority (int): description 2\n'953' sender (str, optional): description 3\n'954" message (str, optional): description 4, defaults to 'hello'\n"955' multiline (str, optional): long description 5,\n'956" defaults to 'bye'",957),958],959)960def test_compose_clean(source: str, expected: str) -> None:961"""Test compose in clean mode."""962assert (963compose(parse(source), rendering_style=RenderingStyle.CLEAN)964== expected965)966967968@pytest.mark.parametrize(969'source, expected',970[971(972"""973Short description974975Args:976name: description 1977priority (int): description 2978sender (str, optional): description 3979message (str, optional): description 4, defaults to 'hello'980multiline (str?):981long description 5,982defaults to 'bye'983""",984'Short description\n'985'\n'986'Args:\n'987' name:\n'988' description 1\n'989' priority (int):\n'990' description 2\n'991' sender (str, optional):\n'992' description 3\n'993' message (str, optional):\n'994" description 4, defaults to 'hello'\n"995' multiline (str, optional):\n'996' long description 5,\n'997" defaults to 'bye'",998),999],1000)1001def test_compose_expanded(source: str, expected: str) -> None:1002"""Test compose in expanded mode."""1003assert (1004compose(parse(source), rendering_style=RenderingStyle.EXPANDED)1005== expected1006)100710081009