Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Generate class include upon writing error #117

Merged
merged 1 commit into from
Sep 8, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions sap/adt/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,16 @@ def __str__(self):
return f'{self.message}'


class ExceptionResourceSaveFailure(ADTError):
"""Thin wrapper for the class type of ADTErrors"""

def __init__(self, message):
super().__init__('com.sap.adt', self.__class__.__name__, message)

def __str__(self):
return f'{self.message}'


def new_adt_error_from_xml(xmldata):
"""Parses the xml data and create the correct instance.

Expand Down
49 changes: 48 additions & 1 deletion sap/adt/objects.py
Original file line number Diff line number Diff line change
Expand Up @@ -783,6 +783,53 @@ def get_headers(self):
return headers


class ClassIncludeEditor(ADTObjectSourceEditorWithResponse):
"""Source Code Editor"""

def __init__(self, include_type, instance, lock_handle=None, corrnr=None):
super().__init__(instance, lock_handle=lock_handle, corrnr=corrnr)

self.include_type = include_type

def generate_includes(self):
"""Generates the corresponding Class Inlcude"""

self.connection.execute(
'POST',
self.uri + '/includes',
headers={'content-type': 'application/vnd.sap.adt.oo.classincludes+xml'},
params=modify_object_params(self.lock_handle, self.corrnr),
body='<?xml version="1.0" encoding="UTF-8"?>'
'<class:abapClassInclude xmlns:class="http://www.sap.com/adt/oo/classes"'
' xmlns:adtcore="http://www.sap.com/adt/core" adtcore:name="dummy"'
f' class:includeType="{self.include_type}"/>'
)

def write(self, content):
"""Tries to write twice. If the first attempt fails, the method blindly
tries to generate the includes (because that is the most commont
root cause of failed writes) and repeats the write attempt.
"""

try:
super().write(content)
# Hooray, the class include exists!!!
return
except sap.adt.errors.ExceptionResourceSaveFailure as ex:
# Oops, the class include most probably does not exist.
mod_log().debug("Writing of the class include has failed: %s", str(ex))
# But we do not give up as that is expected.
# When you create an ABAP class in SAPGUI (or pull by abapGit)
# and then you try to write its test classes over ADT API
# you get the caught error because the test classes include
# was not generated automatically.

# So lets try to generate the class include ...
self.generate_includes()
# and give it one more try - this time unprotected to die on errors
super().write(content)


class ADTObjectPropertyEditor(ADTObjectEditor):
"""Object property modification actions - for objects which have only the
XML and other resource associated.
Expand Down Expand Up @@ -1209,7 +1256,7 @@ def open_editor(self, lock_handle=None, corrnr=None):
if lock_handle is None:
lock_handle = self.lock()

return self._clas.objtype.open_editor(self, lock_handle=lock_handle, corrnr=corrnr)
return ClassIncludeEditor(self.objtype.include_type, self, lock_handle=lock_handle, corrnr=corrnr)

def fetch(self):
"""Retrieve data from ADT"""
Expand Down
4 changes: 4 additions & 0 deletions test/unit/fixtures_adt_clas.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,3 +46,7 @@
<atom:link xmlns:atom="http://www.w3.org/2005/Atom" href="source/main" rel="http://www.sap.com/adt/relations/source" type="text/html" etag="20190202130106001000001"/>
</class:include>
</class:abapClass>'''

WRITE_INCLUDE_ERROR_XML = '''<?xml version="1.0" encoding="utf-8"?><exc:exception xmlns:exc="http://www.sap.com/abapxml/types/communicationframework"><namespace id="com.sap.adt"/><type id="ExceptionResourceSaveFailure"/><message lang="EN">TEST=====CCAU does not have any inactive version</message></exc:exception>'''

GENERATE_INCLUDE_REQUEST_XML = '''<?xml version="1.0" encoding="UTF-8"?><class:abapClassInclude xmlns:class="http://www.sap.com/adt/oo/classes" xmlns:adtcore="http://www.sap.com/adt/core" adtcore:name="dummy" class:includeType="testclasses"/>'''
21 changes: 20 additions & 1 deletion test/unit/test_sap_adt_class.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
from fixtures_adt import (LOCK_RESPONSE_OK, EMPTY_RESPONSE_OK, TEST_CLASSES_READ_RESPONSE_OK,
DEFINITIONS_READ_RESPONSE_OK, IMPLEMENTATIONS_READ_RESPONSE_OK)

from fixtures_adt_clas import CREATE_CLASS_ADT_XML, GET_CLASS_ADT_XML
from fixtures_adt_clas import (CREATE_CLASS_ADT_XML, GET_CLASS_ADT_XML, WRITE_INCLUDE_ERROR_XML,
GENERATE_INCLUDE_REQUEST_XML)


FIXTURE_CLASS_MAIN_CODE='''class zcl_hello_world definition public.
Expand Down Expand Up @@ -135,6 +136,24 @@ def test_adt_class_write_implementations(self):
def test_adt_class_write_tests(self):
self.include_write_test(lambda clas: clas.test_classes, 'includes/testclasses')

def test_adt_class_write_include_with_generate(self):
GENERATE_INCLUDE_REQUEST_ID = 2
GENERATE_INCLUDE_ADT_URI = '/sap/bc/adt/oo/classes/zcl_hello_world/includes'
WRITE_GENERATED_INCLUDE_REQUEST_ID = 3

conn = Connection([LOCK_RESPONSE_OK,
Response(status_code=405, headers={'content-type': 'application/xml'}, text=WRITE_INCLUDE_ERROR_XML),
EMPTY_RESPONSE_OK, # generate test class
EMPTY_RESPONSE_OK, # write test class
None])
clas = sap.adt.Class(conn, 'ZCL_HELLO_WORLD')
with clas.test_classes.open_editor() as editor:
editor.write('* new test')

self.assertEqual(conn.execs[GENERATE_INCLUDE_REQUEST_ID].adt_uri, GENERATE_INCLUDE_ADT_URI)
self.assertEqual(conn.execs[GENERATE_INCLUDE_REQUEST_ID].body, GENERATE_INCLUDE_REQUEST_XML)
self.assertEqual(conn.execs[WRITE_GENERATED_INCLUDE_REQUEST_ID].body, b'* new test')

def include_activate_test(self, getter, includes_uri):
conn = Connection(
[
Expand Down
Loading