Skip to content

Conversation

@bebound
Copy link
Contributor

@bebound bebound commented Mar 6, 2023

Description

ARM64 machines are multi-core, and some errors occur because of concurrency, as all tests use same config dir.

I add a random_config_dir param in DummyCli. If it is True, the config dir will be ~/.azure/dummy_cli_config_dir/xxxxxxx.

If random config is enabled by default, the config file is created for each test, and the performance decrease a lot.
Azure.azure-cli Full Test spends 24 min instead of 16min.

I enable random_config_dir for the tests which use az config or az configure.
Example usage:

class ConfigTest(ScenarioTest):

    def __init__(self, *arg, **kwargs):
        super().__init__(*arg, random_config_dir=True, **kwargs)
2023-03-03T11:05:05.5590254Z [gw2] [ 65%] FAILED tests/test_cloud.py::TestCloud::test_remove_known_cloud 
2023-03-03T11:05:05.5590455Z 
2023-03-03T11:05:05.5590884Z =================================== FAILURES ===================================
2023-03-03T11:05:05.5591177Z ______________________ TestCloud.test_remove_known_cloud _______________________
2023-03-03T11:05:05.5592042Z [gw2] linux -- Python 3.10.10 /opt/az/bin/python3
2023-03-03T11:05:05.5592295Z self = <core.tests.test_cloud.TestCloud testMethod=test_remove_known_cloud>
2023-03-03T11:05:05.5592422Z 
2023-03-03T11:05:05.5592577Z     def test_remove_known_cloud(self):
2023-03-03T11:05:05.5592756Z >       cli = DummyCli()
2023-03-03T11:05:05.5592824Z 
2023-03-03T11:05:05.5593120Z /opt/az/lib/python3.10/site-packages/azure/cli/core/tests/test_cloud.py:216: 
2023-03-03T11:05:05.5593362Z _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
2023-03-03T11:05:05.5593706Z /opt/az/lib/python3.10/site-packages/azure/cli/core/mock.py:25: in __init__
2023-03-03T11:05:05.5593932Z     super(DummyCli, self).__init__(
2023-03-03T11:05:05.5594263Z /opt/az/lib/python3.10/site-packages/azure/cli/core/__init__.py:59: in __init__
2023-03-03T11:05:05.5594502Z     super(AzCli, self).__init__(**kwargs)
2023-03-03T11:05:05.5594818Z /opt/az/lib/python3.10/site-packages/knack/cli.py:82: in __init__
2023-03-03T11:05:05.5595028Z     self.config = config_cls(
2023-03-03T11:05:05.5595524Z /opt/az/lib/python3.10/site-packages/knack/config.py:75: in __init__
2023-03-03T11:05:05.5595793Z     self._config_file_chain.append(_ConfigFile(self.config_dir, self.config_path))
2023-03-03T11:05:05.5596156Z /opt/az/lib/python3.10/site-packages/knack/config.py:195: in __init__
2023-03-03T11:05:05.5596418Z     self.config_parser.read(config_path, encoding=CONFIG_FILE_ENCODING)
2023-03-03T11:05:05.5596652Z /opt/az/lib/python3.10/configparser.py:699: in read
2023-03-03T11:05:05.5596854Z     self._read(fp, filename)
2023-03-03T11:05:05.5597051Z _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
2023-03-03T11:05:05.5597146Z 
2023-03-03T11:05:05.5597321Z self = <configparser.ConfigParser object at 0xffff9808a770>
2023-03-03T11:05:05.5597664Z fp = <_io.TextIOWrapper name='/root/.azure/config' mode='r' encoding='utf-8'>
2023-03-03T11:05:05.5597957Z fpname = '/root/.azure/config'
2023-03-03T11:05:05.5598040Z 
2023-03-03T11:05:05.5598203Z     def _read(self, fp, fpname):
2023-03-03T11:05:05.5598387Z         """Parse a sectioned configuration file.
2023-03-03T11:05:05.5598554Z     
2023-03-03T11:05:05.5598736Z         Each section in a configuration file contains a header, indicated by
2023-03-03T11:05:05.5598980Z         a name in square brackets (`[]`), plus key/value options, indicated by
2023-03-03T11:05:05.5599218Z         `name` and `value` delimited with a specific substring (`=` or `:` by
2023-03-03T11:05:05.5599400Z         default).
2023-03-03T11:05:05.5599542Z     
2023-03-03T11:05:05.5599721Z         Values can span multiple lines, as long as they are indented deeper
2023-03-03T11:05:05.5600070Z         than the first line of the value. Depending on the parser's mode, blank
2023-03-03T11:05:05.5600312Z         lines may be treated as parts of multiline values or ignored.
2023-03-03T11:05:05.5600485Z     
2023-03-03T11:05:05.5600673Z         Configuration files may include comments, prefixed by specific
2023-03-03T11:05:05.5600918Z         characters (`#` and `;` by default). Comments may appear on their own
2023-03-03T11:05:05.5601152Z         in an otherwise empty line or may be entered in lines holding values or
2023-03-03T11:05:05.5601417Z         section names. Please note that comments get stripped off when reading configuration files.
2023-03-03T11:05:05.5601624Z         """
2023-03-03T11:05:05.5601770Z         elements_added = set()
2023-03-03T11:05:05.5601956Z         cursect = None                        # None, or a dictionary
2023-03-03T11:05:05.5602124Z         sectname = None
2023-03-03T11:05:05.5602282Z         optname = None
2023-03-03T11:05:05.5602434Z         lineno = 0
2023-03-03T11:05:05.5602582Z         indent_level = 0
2023-03-03T11:05:05.5602759Z         e = None                              # None, or an exception
2023-03-03T11:05:05.5602950Z         for lineno, line in enumerate(fp, start=1):
2023-03-03T11:05:05.5603141Z             comment_start = sys.maxsize
2023-03-03T11:05:05.5603465Z             # strip inline comments
2023-03-03T11:05:05.5603763Z             inline_prefixes = {p: -1 for p in self._inline_comment_prefixes}
2023-03-03T11:05:05.5604005Z             while comment_start == sys.maxsize and inline_prefixes:
2023-03-03T11:05:05.5604200Z                 next_prefixes = {}
2023-03-03T11:05:05.5604382Z                 for prefix, index in inline_prefixes.items():
2023-03-03T11:05:05.5604589Z                     index = line.find(prefix, index+1)
2023-03-03T11:05:05.5604760Z                     if index == -1:
2023-03-03T11:05:05.5604914Z                         continue
2023-03-03T11:05:05.5605086Z                     next_prefixes[prefix] = index
2023-03-03T11:05:05.5605373Z                     if index == 0 or (index > 0 and line[index-1].isspace()):
2023-03-03T11:05:05.5605592Z                         comment_start = min(comment_start, index)
2023-03-03T11:05:05.5605797Z                 inline_prefixes = next_prefixes
2023-03-03T11:05:05.5605976Z             # strip full line comments
2023-03-03T11:05:05.5606312Z             for prefix in self._comment_prefixes:
2023-03-03T11:05:05.5606506Z                 if line.strip().startswith(prefix):
2023-03-03T11:05:05.5606686Z                     comment_start = 0
2023-03-03T11:05:05.5606840Z                     break
2023-03-03T11:05:05.5607001Z             if comment_start == sys.maxsize:
2023-03-03T11:05:05.5607180Z                 comment_start = None
2023-03-03T11:05:05.5607376Z             value = line[:comment_start].strip()
2023-03-03T11:05:05.5607552Z             if not value:
2023-03-03T11:05:05.5607730Z                 if self._empty_lines_in_values:
2023-03-03T11:05:05.5607927Z                     # add empty line to the value, but only if there was no
2023-03-03T11:05:05.5608119Z                     # comment on the line
2023-03-03T11:05:05.5608294Z                     if (comment_start is None and
2023-03-03T11:05:05.5608466Z                         cursect is not None and
2023-03-03T11:05:05.5608632Z                         optname and
2023-03-03T11:05:05.5608798Z                         cursect[optname] is not None):
2023-03-03T11:05:05.5609098Z                         cursect[optname].append('') # newlines added at join
2023-03-03T11:05:05.5609286Z                 else:
2023-03-03T11:05:05.5609444Z                     # empty line marks end of value
2023-03-03T11:05:05.5609627Z                     indent_level = sys.maxsize
2023-03-03T11:05:05.5609790Z                 continue
2023-03-03T11:05:05.5609940Z             # continuation line?
2023-03-03T11:05:05.5610134Z             first_nonspace = self.NONSPACECRE.search(line)
2023-03-03T11:05:05.5610360Z             cur_indent_level = first_nonspace.start() if first_nonspace else 0
2023-03-03T11:05:05.5610584Z             if (cursect is not None and optname and
2023-03-03T11:05:05.5610779Z                 cur_indent_level > indent_level):
2023-03-03T11:05:05.5610961Z                 cursect[optname].append(value)
2023-03-03T11:05:05.5611153Z             # a section header or option header?
2023-03-03T11:05:05.5611322Z             else:
2023-03-03T11:05:05.5611483Z                 indent_level = cur_indent_level
2023-03-03T11:05:05.5611660Z                 # is it a section header?
2023-03-03T11:05:05.5611831Z                 mo = self.SECTCRE.match(value)
2023-03-03T11:05:05.5611996Z                 if mo:
2023-03-03T11:05:05.5612241Z                     sectname = mo.group('header')
2023-03-03T11:05:05.5612426Z                     if sectname in self._sections:
2023-03-03T11:05:05.5612627Z                         if self._strict and sectname in elements_added:
2023-03-03T11:05:05.5612844Z                             raise DuplicateSectionError(sectname, fpname,
2023-03-03T11:05:05.5613028Z                                                         lineno)
2023-03-03T11:05:05.5613206Z                         cursect = self._sections[sectname]
2023-03-03T11:05:05.5613392Z                         elements_added.add(sectname)
2023-03-03T11:05:05.5613747Z                     elif sectname == self.default_section:
2023-03-03T11:05:05.5613945Z                         cursect = self._defaults
2023-03-03T11:05:05.5614099Z                     else:
2023-03-03T11:05:05.5614259Z                         cursect = self._dict()
2023-03-03T11:05:05.5614443Z                         self._sections[sectname] = cursect
2023-03-03T11:05:05.5614648Z                         self._proxies[sectname] = SectionProxy(self, sectname)
2023-03-03T11:05:05.5614855Z                         elements_added.add(sectname)
2023-03-03T11:05:05.5615136Z                     # So sections can't start with a continuation line
2023-03-03T11:05:05.5615328Z                     optname = None
2023-03-03T11:05:05.5615501Z                 # no section header in the file?
2023-03-03T11:05:05.5615669Z                 elif cursect is None:
2023-03-03T11:05:05.5615870Z                     raise MissingSectionHeaderError(fpname, lineno, line)
2023-03-03T11:05:05.5616063Z                 # an option line?
2023-03-03T11:05:05.5616211Z                 else:
2023-03-03T11:05:05.5616510Z                     mo = self._optcre.match(value)
2023-03-03T11:05:05.5616672Z                     if mo:
2023-03-03T11:05:05.5616951Z                         optname, vi, optval = mo.group('option', 'vi', 'value')
2023-03-03T11:05:05.5617150Z                         if not optname:
2023-03-03T11:05:05.5617332Z                             e = self._handle_error(e, fpname, lineno, line)
2023-03-03T11:05:05.5617548Z                         optname = self.optionxform(optname.rstrip())
2023-03-03T11:05:05.5617742Z                         if (self._strict and
2023-03-03T11:05:05.5617923Z                             (sectname, optname) in elements_added):
2023-03-03T11:05:05.5618134Z >                           raise DuplicateOptionError(sectname, optname,
2023-03-03T11:05:05.5618323Z                                                        fpname, lineno)
2023-03-03T11:05:05.5618826Z E                           configparser.DuplicateOptionError: While reading from '/root/.azure/config' [line 31]: option 'cache_ttl' in section 'core' already exists
2023-03-03T11:05:05.5619016Z 
2023-03-03T11:05:05.5619208Z /opt/az/lib/python3.10/configparser.py:1098: DuplicateOptionError

History Notes

[Component Name 1] BREAKING CHANGE: az command a: Make some customer-facing breaking change
[Component Name 2] az command b: Add some customer-facing feature


This checklist is used to make sure that common guidelines for a pull request are followed.

@azure-client-tools-bot-prd
Copy link

azure-client-tools-bot-prd bot commented Mar 6, 2023

️✔️acr
️✔️2020-09-01-hybrid
️✔️3.10
️✔️3.9
️✔️latest
️✔️3.10
️✔️3.9
️✔️acs
️✔️2020-09-01-hybrid
️✔️3.10
️✔️3.9
️✔️latest
️✔️3.10
️✔️3.9
️✔️advisor
️✔️latest
️✔️3.10
️✔️3.9
️✔️ams
️✔️latest
️✔️3.10
️✔️3.9
️✔️apim
️✔️latest
️✔️3.10
️✔️3.9
️✔️appconfig
️✔️latest
️✔️3.10
️✔️3.9
️✔️appservice
️✔️latest
️✔️3.10
️✔️3.9
️✔️aro
️✔️latest
️✔️3.10
️✔️3.9
️✔️backup
️✔️latest
️✔️3.10
️✔️3.9
️✔️batch
️✔️latest
️✔️3.10
️✔️3.9
️✔️batchai
️✔️latest
️✔️3.10
️✔️3.9
️✔️billing
️✔️latest
️✔️3.10
️✔️3.9
️✔️botservice
️✔️latest
️✔️3.10
️✔️3.9
️✔️cdn
️✔️latest
️✔️3.10
️✔️3.9
️✔️cloud
️✔️latest
️✔️3.10
️✔️3.9
️✔️cognitiveservices
️✔️latest
️✔️3.10
️✔️3.9
️✔️config
️✔️latest
️✔️3.10
️✔️3.9
️✔️configure
️✔️latest
️✔️3.10
️✔️3.9
️✔️consumption
️✔️latest
️✔️3.10
️✔️3.9
️✔️container
️✔️latest
️✔️3.10
️✔️3.9
️✔️core
️✔️2018-03-01-hybrid
️✔️3.10
️✔️3.9
️✔️2019-03-01-hybrid
️✔️3.10
️✔️3.9
️✔️2020-09-01-hybrid
️✔️3.10
️✔️3.9
️✔️latest
️✔️3.10
️✔️3.9
️✔️cosmosdb
️✔️latest
️✔️3.10
️✔️3.9
️✔️databoxedge
️✔️2019-03-01-hybrid
️✔️3.10
️✔️3.9
️✔️2020-09-01-hybrid
️✔️3.10
️✔️3.9
️✔️latest
️✔️3.10
️✔️3.9
️✔️dla
️✔️latest
️✔️3.10
️✔️3.9
️✔️dls
️✔️latest
️✔️3.10
️✔️3.9
️✔️dms
️✔️latest
️✔️3.10
️✔️3.9
️✔️eventgrid
️✔️latest
️✔️3.10
️✔️3.9
️✔️eventhubs
️✔️latest
️✔️3.10
️✔️3.9
️✔️feedback
️✔️latest
️✔️3.10
️✔️3.9
️✔️find
️✔️latest
️✔️3.10
️✔️3.9
️✔️hdinsight
️✔️latest
️✔️3.10
️✔️3.9
️✔️identity
️✔️latest
️✔️3.10
️✔️3.9
️✔️iot
️✔️2019-03-01-hybrid
️✔️3.10
️✔️3.9
️✔️2020-09-01-hybrid
️✔️3.10
️✔️3.9
️✔️latest
️✔️3.10
️✔️3.9
️✔️keyvault
️✔️2018-03-01-hybrid
️✔️3.10
️✔️3.9
️✔️2020-09-01-hybrid
️✔️3.10
️✔️3.9
️✔️latest
️✔️3.10
️✔️3.9
️✔️kusto
️✔️latest
️✔️3.10
️✔️3.9
️✔️lab
️✔️latest
️✔️3.10
️✔️3.9
️✔️managedservices
️✔️latest
️✔️3.10
️✔️3.9
️✔️maps
️✔️latest
️✔️3.10
️✔️3.9
️✔️marketplaceordering
️✔️latest
️✔️3.10
️✔️3.9
️✔️monitor
️✔️latest
️✔️3.10
️✔️3.9
️✔️netappfiles
️✔️latest
️✔️3.10
️✔️3.9
️✔️network
️✔️2018-03-01-hybrid
️✔️3.10
️✔️3.9
️✔️latest
️✔️3.10
️✔️3.9
️✔️policyinsights
️✔️latest
️✔️3.10
️✔️3.9
️✔️privatedns
️✔️latest
️✔️3.10
️✔️3.9
️✔️profile
️✔️latest
️✔️3.10
️✔️3.9
️✔️rdbms
️✔️latest
️✔️3.10
️✔️3.9
️✔️redis
️✔️latest
️✔️3.10
️✔️3.9
️✔️relay
️✔️latest
️✔️3.10
️✔️3.9
️✔️resource
️✔️2018-03-01-hybrid
️✔️3.10
️✔️3.9
️✔️2019-03-01-hybrid
️✔️3.10
️✔️3.9
️✔️latest
️✔️3.10
️✔️3.9
️✔️role
️✔️latest
️✔️3.10
️✔️3.9
️✔️search
️✔️latest
️✔️3.10
️✔️3.9
️✔️security
️✔️latest
️✔️3.10
️✔️3.9
️✔️servicebus
️✔️latest
️✔️3.10
️✔️3.9
️✔️serviceconnector
️✔️latest
️✔️3.10
️✔️3.9
️✔️servicefabric
️✔️latest
️✔️3.10
️✔️3.9
️✔️signalr
️✔️latest
️✔️3.10
️✔️3.9
️✔️sql
️✔️latest
️✔️3.10
️✔️3.9
️✔️sqlvm
️✔️latest
️✔️3.10
️✔️3.9
️✔️storage
️✔️2018-03-01-hybrid
️✔️3.10
️✔️3.9
️✔️2019-03-01-hybrid
️✔️3.10
️✔️3.9
️✔️2020-09-01-hybrid
️✔️3.10
️✔️3.9
️✔️latest
️✔️3.10
️✔️3.9
️✔️synapse
️✔️latest
️✔️3.10
️✔️3.9
️✔️telemetry
️✔️2018-03-01-hybrid
️✔️3.10
️✔️3.9
️✔️2019-03-01-hybrid
️✔️3.10
️✔️3.9
️✔️2020-09-01-hybrid
️✔️3.10
️✔️3.9
️✔️latest
️✔️3.10
️✔️3.9
️✔️util
️✔️latest
️✔️3.10
️✔️3.9
️✔️vm
️✔️2018-03-01-hybrid
️✔️3.10
️✔️3.9
️✔️2019-03-01-hybrid
️✔️3.10
️✔️3.9
️✔️2020-09-01-hybrid
️✔️3.10
️✔️3.9
️✔️latest
️✔️3.10
️✔️3.9

@ghost ghost added Auto-Assign Auto assign by bot CI CI labels Mar 6, 2023
@ghost ghost requested review from jiasli, jsntcy, kairu-ms, wangzelin007 and yonzhan March 6, 2023 06:55
@ghost ghost assigned wangzelin007 Mar 6, 2023
@ghost ghost added this to the March 2023 (2023-04-04) milestone Mar 6, 2023
@yonzhan
Copy link
Collaborator

yonzhan commented Mar 6, 2023

CI

@bebound bebound changed the title {CI} Use random config dir in DummyCli {Core} Add random_config_dir param in DummyCli Mar 8, 2023
self.assertGreaterEqual(len(local_defaults_config), 1)
actual = set([(x['name'], x['source'], x['value']) for x in local_defaults_config if x['name'] == 'group'])
expected = set([('group', os.path.join(temp_dir, '.azure', 'config'), self.kwargs['rg'])])
expected = set([('group', os.path.join(temp_dir, os.path.basename(self.cli_ctx.config.config_dir), 'config'), self.kwargs['rg'])])
Copy link
Contributor Author

@bebound bebound Mar 9, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The config path is temp_dir+basename(config_dir)+'config' when --scope local

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's provide some concrete example.

Copy link
Contributor Author

@bebound bebound Mar 24, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If work dir is C:\Users\hanglei\AppData\Local\Temp\tmplxf_89ez, local config file is C:\Users\hanglei\AppData\Local\Temp\tmplxf_89ez\0azXbKR9OdJuZPFS\config

0azXbKR9OdJuZPFS is the base name of config dir: ~/.azure/dummy_cli_config_dir/0azXbKR9OdJuZPFS/

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The local config folder was constructed by knack.config.CLIConfig:

https://github.com/microsoft/knack/blob/e496c9590792572e680cb3ec959db175d9ba85dd/knack/config.py#L63

config_dir_name = os.path.basename(self.config_dir)
current_config_dir = os.path.join(current_dir, config_dir_name)

current_config_dir is constructed by combining current_dir with the random dir name, making the path a little bit weird.

@bebound bebound marked this pull request as ready for review March 9, 2023 03:59
super(LocalContextScenarioTest, self).__init__(method_name, config_file, recording_name, recording_processors,
replay_processors, recording_patches, replay_patches)
replay_processors, recording_patches, replay_patches,
random_config_dir=True)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ConfigureGlobalDefaultsTest runs config param-persist off in tearDown. I think use random_config_dir is necessary

Copy link
Member

@jiasli jiasli Mar 17, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Local Context/Param Persist has been deprecated. Consider deleting related tests instead: #24726

Comment on lines +30 to +31
config_dir=os.path.join(GLOBAL_CONFIG_DIR, 'dummy_cli_config_dir',
random_string()) if random_config_dir else GLOBAL_CONFIG_DIR,
Copy link
Member

@jiasli jiasli Mar 17, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As an alternative, we may also consider using tempfile module: https://docs.python.org/3/library/tempfile.html

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, sorry I didn't make my point clear. I am simply providing an alternative solution. You solution is actually better since your location is easy to find. 😉

Comment on lines 143 to 144
import shutil
shutil.rmtree(self.cli_ctx.config.config_dir, ignore_errors=True)
Copy link
Member

@jiasli jiasli Mar 17, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well... yes. rmtree can fail intermittently on Windows. Consider using

def rmtree_with_retry(path):

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, yes. I forget that Windows bug.


from knack.completion import ARGCOMPLETE_ENV_NAME

random_config_dir = kwargs.get('random_config_dir', False)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider making it an explicit kwarg.

class RoleAssignmentScenarioTest(RoleScenarioTestBase):

def __init__(self, *arg, **kwargs):
super().__init__(*arg, random_config_dir=True, **kwargs)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

random_config_dir=True make tests under RoleAssignmentScenarioTest fail during live run, because the login context is lost.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, we need to merge #26475 first.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Auto-Assign Auto assign by bot CI CI

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants